[PATCH v3 01/19] drm: Add |struct drm_gem_vram_object| and helpers
Daniel Vetter
daniel at ffwll.ch
Tue Apr 30 14:40:20 UTC 2019
On Mon, Apr 29, 2019 at 09:58:55PM +0200, Sam Ravnborg wrote:
> Hi Thomas.
>
> Some minor things and some bikeshedding too.
>
> One general^Wbikeshedding thing - unint32_t is used in many places.
> And then s64 in one place.
> Seems like two concepts are mixed.
> Maybe be consistent and use u32, s32 everywhere?
>
> I did not read the code carefully enough to understand it.
> I cannot give a r-b or a-b - as I feel I need to understand it to do
> so.
>
> Sam
>
> On Mon, Apr 29, 2019 at 04:43:23PM +0200, Thomas Zimmermann wrote:
> > The type |struct drm_gem_vram_object| implements a GEM object for simple
> > framebuffer devices with dedicated video memory. The BO is either located
> > in VRAM or system memory.
> >
> > The implementation has been created from the respective code in ast,
> > bochs and mgag200. These drivers copy their implementation from each
> > other; except for the names of several data types. The helpers are
> > currently build with TTM, but this is considered an implementation
> > detail and may change in future updates.
> >
> > v2:
> > * rename to |struct drm_gem_vram_object|
> > * move drm_is_gem_ttm() to a later patch in the series
> > * add drm_gem_vram_kmap_at()
> > * return is_iomem from kmap functions
> > * redefine TTM placement flags for public interface
> > * documentation fixes
> >
> > Signed-off-by: Thomas Zimmermann <tzimmermann at suse.de>
> > ---
> > Documentation/gpu/drm-mm.rst | 12 +
> > drivers/gpu/drm/Kconfig | 13 +
> > drivers/gpu/drm/Makefile | 4 +
> > drivers/gpu/drm/drm_gem_vram_helper.c | 410 +++++++++++++++++++++++
> > drivers/gpu/drm/drm_vram_helper_common.c | 6 +
> > include/drm/drm_gem_vram_helper.h | 92 +++++
> > 6 files changed, 537 insertions(+)
> > create mode 100644 drivers/gpu/drm/drm_gem_vram_helper.c
> > create mode 100644 drivers/gpu/drm/drm_vram_helper_common.c
> > create mode 100644 include/drm/drm_gem_vram_helper.h
> >
> > diff --git a/Documentation/gpu/drm-mm.rst b/Documentation/gpu/drm-mm.rst
> > index 54a696d961a7..d5327ed608d7 100644
> > --- a/Documentation/gpu/drm-mm.rst
> > +++ b/Documentation/gpu/drm-mm.rst
> > @@ -380,6 +380,18 @@ GEM CMA Helper Functions Reference
> > .. kernel-doc:: drivers/gpu/drm/drm_gem_cma_helper.c
> > :export:
> >
> > +GEM VRAM Helper Functions Reference
> > +----------------------------------
> > +
> > +.. kernel-doc:: drivers/gpu/drm/drm_gem_vram_helper.c
> > + :doc: overview
> > +
> > +.. kernel-doc:: include/drm/drm_gem_vram_helper.h
> > + :internal:
> > +
> > +.. kernel-doc:: drivers/gpu/drm/drm_gem_vram_helper.c
> > + :export:
> > +
> > VMA Offset Manager
> > ==================
> >
> > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> > index 2267e84d5cb4..c0d49a6c09d2 100644
> > --- a/drivers/gpu/drm/Kconfig
> > +++ b/drivers/gpu/drm/Kconfig
> > @@ -160,6 +160,12 @@ config DRM_TTM
> > GPU memory types. Will be enabled automatically if a device driver
> > uses it.
> >
> > +config DRM_VRAM_HELPER
> > + tristate
> > + depends on DRM && DRM_TTM
> > + help
> > + Helpers for VRAM memory management
> > +
> > config DRM_GEM_CMA_HELPER
> > bool
> > depends on DRM
> > @@ -179,6 +185,13 @@ config DRM_GEM_SHMEM_HELPER
> > help
> > Choose this if you need the GEM shmem helper functions
> >
> > +config DRM_GEM_VRAM_HELPER
> > + bool
> > + depends on DRM
> > + select DRM_VRAM_HELPER
> > + help
> > + Choose this if you need the GEM VRAM helper functions
> > +
> I cannot remember how select will deal with symbols whos has
> a "depends on".
> But if I recall correct then the "depends on" will be ignored
> or in best case trigger a warning.
> In other words - when we have symbols we select they should not
> have a depends on.
>
> What can be done is something like:
>
> symbol foo
> bool
>
> symbol bar
> depends on foo
>
>
> symbol foobar
> bool "This is what you need - select me"
> select foo
>
> So when one chooses "foobar" then we will select "foo" and this will
> satisfy bar.
>
> But maybe this rambling is irrelevant - maybe check what we do with
> other selectable symbols in DRM.
>
>
> > +/**
> > + * DOC: overview
> > + *
> > + * This library provides a GEM object that is backed by VRAM. It
> > + * can be used for simple framebuffer devices with dedicated memory.
> > + */
> It is likely only me, but...
> I could use a short explanation what is GEM and maybe also VRAM.
It exists:
https://dri.freedesktop.org/docs/drm/gpu/drm-mm.html#the-graphics-execution-manager-gem
In the same chapter even where this will be added. I do agree that
explaining a bit more what's meant with VRAM would be good though.
-Daniel
> VRAM as video RAM, but maybe there is more constraints?
> (When I first looked at DRM I wondered what you used Virtual RAM for.
> But thats a long time ago so it counts only as a funny story.
>
> > + * Buffer-objects helpers
> > + */
> > +
> > +static void drm_gem_vram_cleanup(struct drm_gem_vram_object *gbo)
> > +{
> > + /* We got here via ttm_bo_put(), which means that the
> > + * TTM buffer object in 'bo' has already been cleaned
> > + * up; only release the GEM object. */
> > + drm_gem_object_release(&gbo->gem);
> > +}
> > +
> > +static void drm_gem_vram_destroy(struct drm_gem_vram_object *gbo)
> > +{
> > + drm_gem_vram_cleanup(gbo);
> > + kfree(gbo);
> > +}
> > +
> > +static void ttm_buffer_object_destroy(struct ttm_buffer_object *bo)
> > +{
> > + struct drm_gem_vram_object *gbo = drm_gem_vram_of_bo(bo);
> > + drm_gem_vram_destroy(gbo);
> > +}
> > +
> > +static void drm_gem_vram_placement(struct drm_gem_vram_object *gbo, int pl_flag)
> > +{
> > + unsigned int i;
> > + unsigned int c = 0;
> > +
> > + gbo->placement.placement = gbo->placements;
> > + gbo->placement.busy_placement = gbo->placements;
> > +
> > + if (pl_flag & TTM_PL_FLAG_VRAM)
> > + gbo->placements[c++].flags = TTM_PL_FLAG_WC |
> > + TTM_PL_FLAG_UNCACHED |
> > + TTM_PL_FLAG_VRAM;
> > +
> > + if (pl_flag & TTM_PL_FLAG_SYSTEM)
> > + gbo->placements[c++].flags = TTM_PL_MASK_CACHING |
> > + TTM_PL_FLAG_SYSTEM;
> > +
> > + if (!c)
> > + gbo->placements[c++].flags = TTM_PL_MASK_CACHING |
> > + TTM_PL_FLAG_SYSTEM;
> > +
> > + gbo->placement.num_placement = c;
> > + gbo->placement.num_busy_placement = c;
> > +
> > + for (i = 0; i < c; ++i) {
> > + gbo->placements[i].fpfn = 0;
> > + gbo->placements[i].lpfn = 0;
> > + }
> > +}
> > +
> > +static int drm_gem_vram_init(struct drm_device *dev,
> > + struct ttm_bo_device *bdev,
> > + struct drm_gem_vram_object *gbo,
> > + unsigned long size, uint32_t pg_align,
> > + bool interruptible)
> > +{
> > + int ret;
> > + size_t acc_size;
> > +
> > + ret = drm_gem_object_init(dev, &gbo->gem, size);
> > + if (ret)
> > + return ret;
> > +
> > + acc_size = ttm_bo_dma_acc_size(bdev, size, sizeof(*gbo));
> > +
> > + gbo->bo.bdev = bdev;
> > + drm_gem_vram_placement(gbo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM);
> > +
> > + ret = ttm_bo_init(bdev, &gbo->bo, size, ttm_bo_type_device,
> > + &gbo->placement, pg_align, interruptible, acc_size,
> > + NULL, NULL, ttm_buffer_object_destroy);
> > + if (ret)
> > + goto err_drm_gem_object_release;
> > +
> > + return 0;
> > +
> > +err_drm_gem_object_release:
> > + drm_gem_object_release(&gbo->gem);
> > + return ret;
> > +}
> > +
> > +/**
> > + * drm_gem_vram_create() - Creates a VRAM-backed GEM object
> > + * @dev: the DRM device
> > + * @bdev: the TTM BO device backing the object
> > + * @size: the buffer size in bytes
> > + * @pg_align: the buffer's alignment in multiples of the page size
> > + * @interruptible: sleep interruptible if waiting for memory
> > + *
> > + * Returns:
> > + * A new instance of &struct drm_gem_vram_object on success, or
> > + * an ERR_PTR()-encoded error code otherwise.
> > + */
> > +struct drm_gem_vram_object* drm_gem_vram_create(struct drm_device *dev,
> > + struct ttm_bo_device *bdev,
> > + unsigned long size,
> > + uint32_t pg_align,
> > + bool interruptible)
> > +{
> > + struct drm_gem_vram_object *gbo;
> > + int ret;
> > +
> > + gbo = kzalloc(sizeof(*gbo), GFP_KERNEL);
> > + if (!gbo)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + ret = drm_gem_vram_init(dev, bdev, gbo, size, pg_align, interruptible);
> > + if (ret < 0)
> > + goto err_kfree;
> > +
> > + return gbo;
> > +
> > +err_kfree:
> > + kfree(gbo);
> > + return ERR_PTR(ret);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_create);
> > +
> > +/**
> > + * drm_gem_vram_put() - Releases a reference to a VRAM-backed GEM object
> > + * @gbo: the GEM VRAM object
> > + *
> > + * See ttm_bo_put() for more information.
> > + */
> > +void drm_gem_vram_put(struct drm_gem_vram_object *gbo)
> > +{
> > + ttm_bo_put(&gbo->bo);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_put);
> > +
> > +/**
> > + * drm_gem_vram_reserve() - Reserves a VRAM-backed GEM object
> > + * @gbo: the GEM VRAM object
> > + * @no_wait: don't wait for buffer object to become available
> > + *
> > + * See ttm_bo_reserve() for more information.
> > + *
> > + * Returns:
> > + * 0 on success, or
> > + * a negative error code otherwise
> > + */
> > +int drm_gem_vram_reserve(struct drm_gem_vram_object *gbo, bool no_wait)
> > +{
> > + return ttm_bo_reserve(&gbo->bo, true, no_wait, NULL);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_reserve);
> > +
> > +/**
> > + * drm_gem_vram_unreserve() - \
> > + Release a reservation acquired by drm_gem_vram_reserve()
> > + * @gbo: the GEM VRAM object
> > + *
> > + * See ttm_bo_unreserve() for more information.
> > + */
> > +void drm_gem_vram_unreserve(struct drm_gem_vram_object *gbo)
> > +{
> > + ttm_bo_unreserve(&gbo->bo);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_unreserve);
> > +
> > +/**
> > + * drm_gem_vram_mmap_offset() - Returns a GEM VRAM object's mmap offset
> > + * @gbo: the GEM VRAM object
> > + *
> > + * See drm_vma_node_offset_addr() for more information.
> > + *
> > + * Returns:
> > + * The buffer object's offset for userspace mappings on success, or
> > + * 0 if no offset is allocated.
> > + */
> > +u64 drm_gem_vram_mmap_offset(struct drm_gem_vram_object *gbo)
> > +{
> > + return drm_vma_node_offset_addr(&gbo->bo.vma_node);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_mmap_offset);
> > +
> > +/**
> > + * drm_gem_vram_offset() - \
> > + Returns a GEM VRAM object's offset in video memory
> > + * @gbo: the GEM VRAM object
> > + *
> > + * This function returns the buffer object's offset in the device's video
> > + * memory. The buffer object has to be pinned to %TTM_PL_VRAM.
> > + *
> > + * Returns:
> > + * The buffer object's offset in video memory on success, or
> > + * a negative error code otherwise.
> > + */
> > +s64 drm_gem_vram_offset(struct drm_gem_vram_object *gbo)
> > +{
> > + if (!gbo->pin_count)
> > + return (s64)-ENODEV;
> > + return gbo->bo.offset;
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_offset);
> > +
> > +/**
> > + * drm_gem_vram_pin() - Pins a GEM VRAM object in a region.
> > + * @gbo: the GEM VRAM object
> > + * @pl_flag: a bitmask of possible memory regions
> > + *
> > + * Pinning a buffer object ensures that it is not evicted from
> > + * a memory region. A pinned buffer object has to be unpinned before
> > + * it can be pinned to another region.
> > + *
> > + * Returns:
> > + * 0 on success, or
> > + * a negative error code otherwise.
> > + */
> > +int drm_gem_vram_pin(struct drm_gem_vram_object *gbo, u32 pl_flag)
> > +{
> > + int i, ret;
> > + struct ttm_operation_ctx ctx = { false, false };
> > +
> > + if (gbo->pin_count) {
> > + ++gbo->pin_count;
> > + return 0;
> > + }
> > +
> > + drm_gem_vram_placement(gbo, pl_flag);
> > + for (i = 0; i < gbo->placement.num_placement; ++i)
> > + gbo->placements[i].flags |= TTM_PL_FLAG_NO_EVICT;
> > +
> > + ret = ttm_bo_validate(&gbo->bo, &gbo->placement, &ctx);
> > + if (ret < 0)
> > + return ret;
> > +
> > + gbo->pin_count = 1;
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_pin);
> > +
> > +/**
> > + * drm_gem_vram_unpin() - Unpins a GEM VRAM object
> > + * @gbo: the GEM VRAM object
> > + *
> > + * Returns:
> > + * 0 on success, or
> > + * a negative error code otherwise.
> > + */
> > +int drm_gem_vram_unpin(struct drm_gem_vram_object *gbo)
> > +{
> > + int i, ret;
> > + struct ttm_operation_ctx ctx = { false, false };
> > +
> > + if (!gbo->pin_count)
> > + return 0;
> > +
> > + --gbo->pin_count;
> > + if (gbo->pin_count)
> > + return 0;
> > +
> > + for (i = 0; i < gbo->placement.num_placement ; ++i)
> > + gbo->placements[i].flags &= ~TTM_PL_FLAG_NO_EVICT;
> > +
> > + ret = ttm_bo_validate(&gbo->bo, &gbo->placement, &ctx);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_unpin);
> > +
> > +/**
> > + * drm_gem_vram_push_to_system() - \
> > + Unpins a GEM VRAM object and moves it to system memory
> > + * @gbo: the GEM VRAM object
> > + *
> > + * This operation only works if the caller holds the final pin on the
> > + * buffer object.
> > + *
> > + * Returns:
> > + * 0 on success, or
> > + * a negative error code otherwise.
> > + */
> > +int drm_gem_vram_push_to_system(struct drm_gem_vram_object *gbo)
> > +{
> > + int i, ret;
> > + struct ttm_operation_ctx ctx = { false, false };
> > +
> > + if (!gbo->pin_count)
> > + return 0;
> > +
> > + --gbo->pin_count;
> > + if (gbo->pin_count)
> > + return 0;
> > +
> > + if (gbo->kmap.virtual)
> > + ttm_bo_kunmap(&gbo->kmap);
> > +
> > + drm_gem_vram_placement(gbo, TTM_PL_FLAG_SYSTEM);
> > + for (i = 0; i < gbo->placement.num_placement ; ++i)
> > + gbo->placements[i].flags |= TTM_PL_FLAG_NO_EVICT;
> > +
> > + ret = ttm_bo_validate(&gbo->bo, &gbo->placement, &ctx);
> > + if (ret)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_push_to_system);
> > +
> > +/**
> > + * drm_gem_vram_kmap_at() - Maps a GEM VRAM object into kernel address space
> > + * @gbo: the GEM VRAM object
> > + * @map: establish a mapping if necessary
> > + * @is_iomem: returns true if the mapped memory is I/O memory, or false \
> > + otherwise; can be NULL
> > + * @kmap: the mapping's kmap object
> > + *
> > + * This function maps the buffer object into the kernel's address space
> > + * or returns the current mapping. If the parameter map is false, the
> > + * function only queries the current mapping, but does not establish a
> > + * new one.
> > + *
> > + * Returns:
> > + * The buffers virtual address if mapped, or
> > + * NULL if not mapped, or
> > + * an ERR_PTR()-encoded error code otherwise.
> > + */
> > +void* drm_gem_vram_kmap_at(struct drm_gem_vram_object *gbo, bool map,
> > + bool *is_iomem, struct ttm_bo_kmap_obj *kmap)
> > +{
> > + int ret;
> > +
> > + if (kmap->virtual || !map)
> > + goto out;
> > +
> > + ret = ttm_bo_kmap(&gbo->bo, 0, gbo->bo.num_pages, kmap);
> > + if (ret)
> > + return ERR_PTR(ret);
> > +
> > +out:
> > + if (!is_iomem) {
> > + return kmap->virtual;
> > + }
> > + if (!kmap->virtual) {
> > + *is_iomem = false;
> > + return NULL;
> > + }
> > + return ttm_kmap_obj_virtual(kmap, is_iomem);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_kmap_at);
> > +
> > +/**
> > + * drm_gem_vram_kmap() - Maps a GEM VRAM object into kernel address space
> > + * @gbo: the GEM VRAM object
> > + * @map: establish a mapping if necessary
> > + * @is_iomem: returns true if the mapped memory is I/O memory, or false \
> > + otherwise; can be NULL
> > + *
> > + * This function maps the buffer object into the kernel's address space
> > + * or returns the current mapping. If the parameter map is false, the
> > + * function only queries the current mapping, but does not establish a
> > + * new one.
> > + *
> > + * Returns:
> > + * The buffers virtual address if mapped, or
> > + * NULL if not mapped, or
> > + * an ERR_PTR()-encoded error code otherwise.
> > + */
> > +void* drm_gem_vram_kmap(struct drm_gem_vram_object *gbo, bool map,
> > + bool *is_iomem)
> > +{
> > + return drm_gem_vram_kmap_at(gbo, map, is_iomem, &gbo->kmap);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_kmap);
> > +
> > +/**
> > + * drm_gem_vram_kunmap_at() - Unmaps a GEM VRAM object
> > + * @gbo: the GEM VRAM object
> > + * @kmap: the mapping's kmap object
> > + */
> > +void drm_gem_vram_kunmap_at(struct drm_gem_vram_object *gbo,
> > + struct ttm_bo_kmap_obj *kmap)
> > +{
> > + if (!kmap->virtual)
> > + return;
> > +
> > + ttm_bo_kunmap(kmap);
> > + kmap->virtual = NULL;
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_kunmap_at);
> > +
> > +/**
> > + * drm_gem_vram_kunmap() - Unmaps a GEM VRAM object
> > + * @gbo: the GEM VRAM object
> > + */
> > +void drm_gem_vram_kunmap(struct drm_gem_vram_object *gbo)
> > +{
> > + drm_gem_vram_kunmap_at(gbo, &gbo->kmap);
> > +}
> > +EXPORT_SYMBOL(drm_gem_vram_kunmap);
> > diff --git a/drivers/gpu/drm/drm_vram_helper_common.c b/drivers/gpu/drm/drm_vram_helper_common.c
> > new file mode 100644
> > index 000000000000..76b6569c9aad
> > --- /dev/null
> > +++ b/drivers/gpu/drm/drm_vram_helper_common.c
> > @@ -0,0 +1,6 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +
> > +#include <linux/module.h>
> > +
> > +MODULE_DESCRIPTION("DRM VRAM memory-management helpers");
> > +MODULE_LICENSE("GPL");
> > diff --git a/include/drm/drm_gem_vram_helper.h b/include/drm/drm_gem_vram_helper.h
> > new file mode 100644
> > index 000000000000..167616f552e5
> > --- /dev/null
> > +++ b/include/drm/drm_gem_vram_helper.h
> > @@ -0,0 +1,92 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +
> > +#ifndef DRM_GEM_VRAM_HELPER_H
> > +#define DRM_GEM_VRAM_HELPER_H
> > +
> > +#include <drm/drm_gem.h>
> > +#include <drm/ttm/ttm_bo_api.h>
> > +#include <drm/ttm/ttm_placement.h>
> > +#include <linux/kernel.h> /* for container_of() */
> > +
> > +struct filp;
> > +
> > +#define DRM_GEM_VRAM_PL_FLAG_VRAM TTM_PL_FLAG_VRAM
> > +#define DRM_GEM_VRAM_PL_FLAG_SYSTEM TTM_PL_FLAG_SYSTEM
> > +
> > +/*
> > + * Buffer-object helpers
> > + */
> > +
> > +/**
> > + * struct drm_gem_vram_object - GEM object backed by VRAM
> > + * @gem: GEM object
> > + * @bo: TTM buffer object
> > + * @kmap: Mapping information for @bo
> > + * @placement: TTM placement information. Supported placements are \
> > + %TTM_PL_VRAM and %TTM_PL_SYSTEM
> > + * @placements: TTM placement information.
> > + * @pin_count: Pin counter
> > + *
> > + * The type struct drm_gem_vram_object represents a GEM object that is
> > + * backed by VRAM. It can be used for simple frambuffer devices with
> > + * dedicated memory. The buffer object can be evicted to system memory if
> > + * video memory becomes scarce.
> > + */
> > +struct drm_gem_vram_object {
> > + struct drm_gem_object gem;
> > + struct ttm_buffer_object bo;
> > + struct ttm_bo_kmap_obj kmap;
> > +
> > + /* Supported placements are %TTM_PL_VRAM and %TTM_PL_SYSTEM */
> > + struct ttm_placement placement;
> > + struct ttm_place placements[3];
> > +
> > + int pin_count;
> > +};
> Use tabs for indent - not spaces.
> Ask checkpatch if anything similar needs to be adjusted.
>
>
> > +
> > +/**
> > + * Returns the container of type &struct drm_gem_vram_object
> > + * for field bo.
> > + * @bo: the VRAM buffer object
> > + * Returns: The containing GEM VRAM object
> > + */
> > +static inline struct drm_gem_vram_object* drm_gem_vram_of_bo(
> > + struct ttm_buffer_object *bo)
> > +{
> > + return container_of(bo, struct drm_gem_vram_object, bo);
> > +}
> Indent funny. USe same indent as used in other parts of file for
> function arguments.
>
> > +
> > +/**
> > + * Returns the container of type &struct drm_gem_vram_object
> > + * for field gem.
> > + * @gem: the GEM object
> > + * Returns: The containing GEM VRAM object
> > + */
> > +static inline struct drm_gem_vram_object* drm_gem_vram_of_gem(
> > + struct drm_gem_object *gem)
> > +{
> > + return container_of(gem, struct drm_gem_vram_object, gem);
> > +}
> ditto
>
> > +
> > +struct drm_gem_vram_object* drm_gem_vram_create(struct drm_device *dev,
> > + struct ttm_bo_device* bdev,
> > + unsigned long size,
> > + uint32_t pg_align,
> > + bool interruptible);
>
> Here is is "normal"
>
--
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
More information about the dri-devel
mailing list