blob: d3a44e2ffa725fd565be60be9880bc2c3060986e [file] [log] [blame]
Introduction
'genlock' is an in-kernel API and optional userspace interface for a generic
cross-process locking mechanism. The API is designed for situations where
multiple user space processes and/or kernel drivers need to coordinate access
to a shared resource, such as a graphics buffer. The API was designed with
graphics buffers in mind, but is sufficiently generic to allow it to be
independently used with different types of resources. The chief advantage
of genlock over other cross-process locking mechanisms is that the resources
can be accessed by both userspace and kernel drivers which allows resources
to be locked or unlocked by asynchronous events in the kernel without the
intervention of user space.
As an example, consider a graphics buffer that is shared between a rendering
application and a compositing window manager. The application renders into a
buffer. That buffer is reused by the compositing window manager as a texture.
To avoid corruption, access to the buffer needs to be restricted so that one
is not drawing on the surface while the other is reading. Locks can be
explicitly added between the rendering stages in the processes, but explicit
locks require that the application wait for rendering and purposely release the
lock. An implicit release triggered by an asynchronous event from the GPU
kernel driver, however, will let execution continue without requiring the
intercession of user space.
SW Goals
The genlock API implements exclusive write locks and shared read locks meaning
that there can only be one writer at a time, but multiple readers. Processes
that are unable to acquire a lock can be optionally blocked until the resource
becomes available.
Locks are shared between processes. Each process will have its own private
instance for a lock known as a handle. Handles can be shared between user
space and kernel space to allow a kernel driver to unlock or lock a buffer
on behalf of a user process.
Kernel API
Access to the genlock API can either be via the in-kernel API or via an
optional character device (/dev/genlock). The character device is primarily
to be used for legacy resource sharing APIs that cannot be easily changed.
New resource sharing APIs from this point should implement a scheme specific
wrapper for locking.
To create or attach to an existing lock, a process or kernel driver must first
create a handle. Each handle is linked to a single lock at any time. An entityi
may have multiple handles, each associated with a different lock. Once a handle
has been created, the owner may create a new lock or attach an existing lock
that has been exported from a different handle.
Once the handle has a lock attached, the owning process may attempt to lock the
buffer for read or write. Write locks are exclusive, meaning that only one
process may acquire it at any given time. Read locks are shared, meaning that
multiple readers can hold the lock at the same time. Attempts to acquire a read
lock with a writer active or a write lock with one or more readers or writers
active will typically cause the process to block until the lock is acquired.
When the lock is released, all waiting processes will be woken up. Ownership
of the lock is reference counted, meaning that any one owner can "lock"
multiple times. The lock will only be released from the owner when all the
references to the lock are released via unlock.
The owner of a write lock may atomically convert the lock into a read lock
(which will wake up other processes waiting for a read lock) without first
releasing the lock. The owner would simply issue a new request for a read lock.
However, the owner of a read lock cannot convert it into a write lock in the
same manner. To switch from a read lock to a write lock, the owner must
release the lock and then try to reacquire it.
These are the in-kernel API calls that drivers can use to create and
manipulate handles and locks. Handles can either be created and managed
completely inside of kernel space, or shared from user space via a file
descriptor.
* struct genlock_handle *genlock_get_handle(void)
Create a new handle.
* struct genlock_handle * genlock_get_handle_fd(int fd)
Given a valid file descriptor, return the handle associated with that
descriptor.
* void genlock_put_handle(struct genlock_handle *)
Release a handle.
* struct genlock * genlock_create_lock(struct genlock_handle *)
Create a new lock and attach it to the handle.
* struct genlock * genlock_attach_lock(struct genlock_handle *handle, int fd)
Given a valid file descriptor, get the lock associated with it and attach it to
the handle.
* void genlock_release_lock(struct genlock_handle *)
Release a lock attached to a handle.
* int genlock_lock(struct genlock_handle *, int op, int flags, u32 timeout)
Lock or unlock the lock attached to the handle. A zero timeout value will
be treated just like if the GENOCK_NOBLOCK flag is passed; if the lock
can be acquired without blocking then do so otherwise return -EAGAIN.
Function returns -ETIMEDOUT if the timeout expired or 0 if the lock was
acquired.
* int genlock_wait(struct genloc_handle *, u32 timeout)
Wait for a lock held by the handle to go to the unlocked state. A non-zero
timeout value must be passed. Returns -ETIMEDOUT if the timeout expired or
0 if the lock is in an unlocked state.
Character Device
Opening an instance to the /dev/genlock character device will automatically
create a new handle. All ioctl functions with the exception of NEW and
RELEASE use the following parameter structure:
struct genlock_lock {
int fd; /* Returned by EXPORT, used by ATTACH */
int op; /* Used by LOCK */
int flags; /* used by LOCK */
u32 timeout; /* Used by LOCK and WAIT */
}
*GENLOCK_IOC_NEW
Create a new lock and attaches it to the handle. Returns -EINVAL if the handle
already has a lock attached (use GENLOCK_IOC_RELEASE to remove it). Returns
-ENOMEM if the memory for the lock can not be allocated. No data is passed
from the user for this ioctl.
*GENLOCK_IOC_EXPORT
Export the currently attached lock to a file descriptor. The file descriptor
is returned in genlock_lock.fd.
*GENLOCK_IOC_ATTACH
Attach an exported lock file descriptor to the current handle. Return -EINVAL
if the handle already has a lock attached (use GENLOCK_IOC_RELEASE to remove
it). Pass the file descriptor in genlock_lock.fd.
*GENLOCK_IOC_LOCK
Lock or unlock the attached lock. Pass the desired operation in
genlock_lock.op:
* GENLOCK_WRLOCK - write lock
* GENLOCK_RDLOCK - read lock
* GENLOCK_UNLOCK - unlock an existing lock
Pass flags in genlock_lock.flags:
* GENLOCK_NOBLOCK - Do not block if the lock is already taken
Pass a timeout value in milliseconds in genlock_lock.timeout.
genlock_lock.flags and genlock_lock.timeout are not used for UNLOCK.
Returns -EINVAL if no lock is attached, -EAGAIN if the lock is taken and
NOBLOCK is specified or if the timeout value is zero, -ETIMEDOUT if the timeout
expires or 0 if the lock was successful.
* GENLOCK_IOC_WAIT
Wait for the lock attached to the handle to be released (i.e. goes to unlock).
This is mainly used for a thread that needs to wait for a peer to release a
lock on the same shared handle. A non-zero timeout value in milliseconds is
passed in genlock_lock.timeout. Returns 0 when the lock has been released,
-EINVAL if a zero timeout is passed, or -ETIMEDOUT if the timeout expires.
* GENLOCK_IOC_RELEASE
Use this to release an existing lock. This is useful if you wish to attach a
different lock to the same handle. You do not need to call this under normal
circumstances; when the handle is closed the reference to the lock is released.
No data is passed from the user for this ioctl.