[Intel-gfx] [RFC v3 09/12] drm: Add API for in-kernel clients

Noralf Trønnes noralf at tronnes.org
Mon Mar 12 20:21:40 UTC 2018


Den 12.03.2018 17.51, skrev Daniel Vetter:
> On Thu, Mar 08, 2018 at 06:12:11PM +0100, Noralf Tr??nnes wrote:
>> Den 06.03.2018 09.56, skrev Daniel Vetter:
>>> On Thu, Feb 22, 2018 at 09:06:50PM +0100, Noralf Tr??nnes wrote:
>>>> This adds an API for writing in-kernel clients.
>>>>
>>>> TODO:
>>>> - Flesh out and complete documentation.
>>>> - Cloned displays is not tested.
>>>> - Complete tiled display support and test it.
>>>> - Test plug/unplug different monitors.
>>>> - A runtime knob to prevent clients from attaching for debugging purposes.
>>>> - Maybe a way to unbind individual client instances.
>>>> - Maybe take the sysrq support in drm_fb_helper and move it here somehow.
>>>> - Add suspend/resume callbacks.
>>>>     Does anyone know why fbdev requires suspend/resume?
>>>>
>>>> Signed-off-by: Noralf Tr??nnes <noralf at tronnes.org>
>>> The core client api I like. Some of the opens I'm seeing:
>>>
>>> - If we go with using the internal kms api directly instead of IOCTL
>>>     wrappers then a huge pile of the functions you have here aren't needed
>>>     (e.g. all the event stuff we can just directly use vblank events instead
>>>     of all the wrapping). I'm leaning ever more into that direction, since
>>>     much less code to add.
>> Looking at drm_fb_helper once again I now see an opportunity to simplify
>> the modesetting code by nuking drm_fb_helper_connector and stop
>> maintaining an array of connectors. It looks to be possible to just
>> create an array temporarily in drm_setup_crtcs() for the duration of the
>> function. The connectors we care about are ref counted and attached to
>> modesets. This would remove the need for drm_fb_helper_add_one_connector().
>>
>> So I might be able to do struct drm_fb_helper_crtc -> drm_client_crtc
>> and let the client API take over drm_setup_crtcs(). I'll give it a try.
> I'm more wondering why we need drm_client_crtc at all, why is drm_crtc not
> good enough. Or maybe I'm missing something. Imo ioctl wrappers should be
> the exception where we really, really need them (because the backend of
> the ioctl isn't implemented in a generic way, e.g. dumb buffers), not for
> stuff where we already have a perfectly useable in-kernel abi (anything
> related to modesetting).

I was talking about moving the modesetting code from drm_fb_helper.c
to drm_client.c, which meant moving 'struct drm_fb_helper_crtc' as well.

struct drm_fb_helper_crtc {
     struct drm_mode_set mode_set;
     struct drm_display_mode *desired_mode;
     int x, y;
};

But maybe we can get rid of that struct as well.
The info it contains is also available in drm_mode_set:

static void drm_setup_crtcs(struct drm_fb_helper *fb_helper,
                 u32 width, u32 height)
{
...
     drm_fb_helper_for_each_connector(fb_helper, i) {
         struct drm_display_mode *mode = modes[i];
         struct drm_fb_helper_crtc *fb_crtc = crtcs[i];
         struct drm_fb_offset *offset = &offsets[i];

         if (mode && fb_crtc) {
             struct drm_mode_set *modeset = &fb_crtc->mode_set;

             fb_crtc->desired_mode = mode;
             fb_crtc->x = offset->x;
             fb_crtc->y = offset->y;
             modeset->mode = drm_mode_duplicate(dev,
                                fb_crtc->desired_mode);
             modeset->x = offset->x;
             modeset->y = offset->y;
         }
     }

I took the hint about my ioctl wrappers :-)

Noralf.

>
> And in a way the ioctl wrappers wouldn't really be ioctl wrappers
> conceptually, but simple share some of the same code with the ioctl call
> chain. The idea is to provide some minimal wrappar around the ->dumb*
> callbacks.
>
> Anything else is not needed, I think.
>
>> There is one challenge I see upfront and that's the i915 fb_helper
>> callback in drm_setup_crtcs().
>>
>>> - The register/unregister model needs more thought. Allowing both clients
>>>     to register whenever they want to, and drm_device instances to come and
>>>     go is what fbcon has done, and the resulting locking is a horror show.
>>>
>>>     I think if we require that all in-kernel drm_clients are registers when
>>>     loading drm.ko (and enabled/disabled only per module options and
>>>     Kconfig), then we can throw out all the locking. That avoids a lot of
>>>     the headaches.
>>>
>>>     2nd, if the list of clients is static over the lifetime of drm.ko, we
>>>     also don't need to iterate existing drivers. Which avoids me having to
>>>     review the iterator patch (that's the other aspect where fbcon totally
>>>     falls over and essentially just ignores a bunch of races).
>> Are you talking about linking the clients into drm.ko?
>>
>> drivers/gpu/drm/Makefile:
>>
>> drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += client/drm_bootsplash.o
>>
>> drivers/gpu/drm/drm_drv.c:
>>
>> ??static int __init drm_core_init(void)
>> ??{
>> +?????? drm_bootsplash_register();
>> +?????? drm_fbdev_register();
>> ??}
>>
>> drivers/gpu/drm/drm_internal.h:
>>
>> #ifdef DRM_CLIENT_BOOTSPLASH
>> void drm_bootsplash_register(void);
>> #else
>> static inline void drm_bootsplash_register(void)
>> {
>> }
>> #endif
>>
>> drivers/gpu/drm/client/drm_bootsplash.c:
>>
>> static const struct drm_client_funcs drm_bootsplash_funcs = {
>> ?????? .name?????? ?????? = "drm_bootsplash",
>> ?????? .new?????? ?????? = drm_bootsplash_new,
>> ?????? .remove?????? ?????? = drm_bootsplash_remove,
>> ?????? .hotplug?????? = drm_bootsplash_hotplug,
>> };
>>
>> void drm_bootsplash_register(void)
>> {
>> ?????? drm_client_register(&drm_bootsplash_funcs);
>> }
>>
>> drivers/gpu/drm/drm_client.c:
>>
>> static LIST_HEAD(drm_client_funcs_list);
>>
>> void drm_client_register(const struct drm_client_funcs *funcs)
>> {
>> ?????? struct drm_client_funcs_entry *funcs_entry;
>>
>> ?????? funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
>> ?????? if (!funcs_entry) {
>> ?????? ?????? DRM_ERROR("Failed to register: %s\n", funcs->name);
>> ?????? ?????? return;
>> ?????? }
>>
>> ?????? funcs_entry->funcs = funcs;
>>
>> ?????? list_add(&funcs_entry->list, &drm_client_funcs_list);
>>
>> ?????? DRM_DEBUG_KMS("%s\n", funcs->name);
>> }
>>
>>
>> And each client having a runtime enable/disable knob:
>>
>> drivers/gpu/drm/client/drm_bootsplash.c:
>>
>> static bool drm_bootsplash_enabled = true;
>> module_param_named(bootsplash_enabled, drm_bootsplash_enabled, bool, 0600);
>> MODULE_PARM_DESC(bootsplash_enabled, "Enable bootsplash client
>> [default=true]");
>
> Yup, pretty much.
>
>> Simple USB Display
>> A few months back while looking at the udl shmem code, I got the idea
>> that I could turn a Raspberry Pi Zero into a $5 USB to HDMI/DSI/DPI/DBI/TV
>> adapter. The host side would be a simple tinydrm driver using the kernel
>> compression lib to speed up transfers. The gadget/device side would be a
>> userspace app decompressing the buffer into an exported dumb buffer.
>>
>> While working with this client API I realized that I could use it and
>> write a kernel gadget driver instead avoiding the challenge of going
>> back and forth to userspace with the framebuffer. For such a client I
>> would have preferred it to be a loadable module not linked into drm.ko
>> to increase the chance that distributions would enable it.
> I think that we can do as a loadable client, since you probably want some
> configfs interface to define which drm driver it should control. The
> reason behind the static list of built-in clients is purely for the
> auto-register stuff that we want for fbdev emulation and other things.
> Auto-registration where both sides can be loaded in any order is real pain
> wrt locking. Explicit registration where you can load both sides is
> totally fine and I think would cover your usb gadget display driver
> use-case.
>
> btw usb gadget driver to drive drm kms drivers sounds like a really cool
> thing.
> -Daniel
>
>> Noralf.
>>
>>>> ---
>>>>    drivers/gpu/drm/Kconfig             |    2 +
>>>>    drivers/gpu/drm/Makefile            |    3 +-
>>>>    drivers/gpu/drm/client/Kconfig      |    4 +
>>>>    drivers/gpu/drm/client/Makefile     |    1 +
>>>>    drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++
>>> I'd move this into main drm/ directory, it's fairly core stuff.
>>>
>>>>    drivers/gpu/drm/drm_drv.c           |    6 +
>>>>    drivers/gpu/drm/drm_file.c          |    3 +
>>>>    drivers/gpu/drm/drm_probe_helper.c  |    3 +
>>>>    include/drm/drm_client.h            |  192 +++++
>>>>    include/drm/drm_device.h            |    1 +
>>>>    include/drm/drm_file.h              |    7 +
>>>>    11 files changed, 1833 insertions(+), 1 deletion(-)
>>>>    create mode 100644 drivers/gpu/drm/client/Kconfig
>>>>    create mode 100644 drivers/gpu/drm/client/Makefile
>>>>    create mode 100644 drivers/gpu/drm/client/drm_client.c
>>>>    create mode 100644 include/drm/drm_client.h
>>>>
>>>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
>>>> index deeefa7a1773..d4ae15f9ee9f 100644
>>>> --- a/drivers/gpu/drm/Kconfig
>>>> +++ b/drivers/gpu/drm/Kconfig
>>>> @@ -154,6 +154,8 @@ config DRM_SCHED
>>>>    	tristate
>>>>    	depends on DRM
>>>> +source "drivers/gpu/drm/client/Kconfig"
>>>> +
>>>>    source "drivers/gpu/drm/i2c/Kconfig"
>>>>    source "drivers/gpu/drm/arm/Kconfig"
>>>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>>>> index 50093ff4479b..8e06dc7eeca1 100644
>>>> --- a/drivers/gpu/drm/Makefile
>>>> +++ b/drivers/gpu/drm/Makefile
>>>> @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
>>>>    		drm_encoder.o drm_mode_object.o drm_property.o \
>>>>    		drm_plane.o drm_color_mgmt.o drm_print.o \
>>>>    		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
>>>> -		drm_syncobj.o drm_lease.o
>>>> +		drm_syncobj.o drm_lease.o client/drm_client.o
>>>>    drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
>>>>    drm-$(CONFIG_DRM_VM) += drm_vm.o
>>>> @@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
>>>>    obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
>>>>    obj-$(CONFIG_DRM_PL111) += pl111/
>>>>    obj-$(CONFIG_DRM_TVE200) += tve200/
>>>> +obj-y			+= client/
>>>> diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
>>>> new file mode 100644
>>>> index 000000000000..4bb8e4655ff7
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/client/Kconfig
>>>> @@ -0,0 +1,4 @@
>>>> +menu "DRM Clients"
>>>> +	depends on DRM
>>>> +
>>>> +endmenu
>>>> diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
>>>> new file mode 100644
>>>> index 000000000000..f66554cd5c45
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/client/Makefile
>>>> @@ -0,0 +1 @@
>>>> +# SPDX-License-Identifier: GPL-2.0
>>>> diff --git a/drivers/gpu/drm/client/drm_client.c b/drivers/gpu/drm/client/drm_client.c
>>>> new file mode 100644
>>>> index 000000000000..a633bf747316
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/client/drm_client.c
>>>> @@ -0,0 +1,1612 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +// Copyright 2018 Noralf Tr??nnes
>>>> +
>>>> +#include <linux/dma-buf.h>
>>>> +#include <linux/list.h>
>>>> +#include <linux/mutex.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/slab.h>
>>>> +
>>>> +#include <drm/drm_client.h>
>>>> +#include <drm/drm_connector.h>
>>>> +#include <drm/drm_drv.h>
>>>> +#include <drm/drm_file.h>
>>>> +#include <drm/drm_ioctl.h>
>>>> +#include <drm/drmP.h>
>>>> +
>>>> +#include "drm_crtc_internal.h"
>>>> +#include "drm_internal.h"
>>>> +
>>>> +struct drm_client_funcs_entry {
>>>> +	struct list_head list;
>>>> +	const struct drm_client_funcs *funcs;
>>>> +};
>>>> +
>>>> +static LIST_HEAD(drm_client_list);
>>> I think the client list itself should be on the drm_device, not in one
>>> global list that mixes up all the clients of all the drm_devices.
>>>
>>> I'll skip reviewing details since we have a bunch of high-level questions
>>> to figure out first.
>>> -Daniel
>>>
>>>> +static LIST_HEAD(drm_client_funcs_list);
>>>> +static DEFINE_MUTEX(drm_client_list_lock);
>>>> +
>>>> +static void drm_client_new(struct drm_device *dev,
>>>> +			   const struct drm_client_funcs *funcs)
>>>> +{
>>>> +	struct drm_client_dev *client;
>>>> +	int ret;
>>>> +
>>>> +	lockdep_assert_held(&drm_client_list_lock);
>>>> +
>>>> +	client = kzalloc(sizeof(*client), GFP_KERNEL);
>>>> +	if (!client)
>>>> +		return;
>>>> +
>>>> +	mutex_init(&client->lock);
>>>> +	client->dev = dev;
>>>> +	client->funcs = funcs;
>>>> +
>>>> +	ret = funcs->new(client);
>>>> +	DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
>>>> +	if (ret) {
>>>> +		drm_client_free(client);
>>>> +		return;
>>>> +	}
>>>> +
>>>> +	list_add(&client->list, &drm_client_list);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_free - Free DRM client resources
>>>> + * @client: DRM client
>>>> + *
>>>> + * This is called automatically on client removal unless the client returns
>>>> + * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
>>>> + * this when it can't close &drm_file because userspace has an open fd.
>>>> + */
>>>> +void drm_client_free(struct drm_client_dev *client)
>>>> +{
>>>> +	DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
>>>> +	if (WARN_ON(client->file)) {
>>>> +		client->file_ref_count = 1;
>>>> +		drm_client_put_file(client);
>>>> +	}
>>>> +	mutex_destroy(&client->lock);
>>>> +	kfree(client->crtcs);
>>>> +	kfree(client);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_free);
>>>> +
>>>> +static void drm_client_remove(struct drm_client_dev *client)
>>>> +{
>>>> +	lockdep_assert_held(&drm_client_list_lock);
>>>> +
>>>> +	list_del(&client->list);
>>>> +
>>>> +	if (!client->funcs->remove || !client->funcs->remove(client))
>>>> +		drm_client_free(client);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_register - Register a DRM client
>>>> + * @funcs: Client callbacks
>>>> + *
>>>> + * Returns:
>>>> + * Zero on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_register(const struct drm_client_funcs *funcs)
>>>> +{
>>>> +	struct drm_client_funcs_entry *funcs_entry;
>>>> +	struct drm_device_list_iter iter;
>>>> +	struct drm_device *dev;
>>>> +
>>>> +	funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
>>>> +	if (!funcs_entry)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	funcs_entry->funcs = funcs;
>>>> +
>>>> +	mutex_lock(&drm_global_mutex);
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +
>>>> +	drm_device_list_iter_begin(&iter);
>>>> +	drm_for_each_device_iter(dev, &iter)
>>>> +		if (drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +			drm_client_new(dev, funcs);
>>>> +	drm_device_list_iter_end(&iter);
>>>> +
>>>> +	list_add(&funcs_entry->list, &drm_client_funcs_list);
>>>> +
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +	mutex_unlock(&drm_global_mutex);
>>>> +
>>>> +	DRM_DEBUG_KMS("%s\n", funcs->name);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_register);
>>>> +
>>>> +/**
>>>> + * drm_client_unregister - Unregister a DRM client
>>>> + * @funcs: Client callbacks
>>>> + */
>>>> +void drm_client_unregister(const struct drm_client_funcs *funcs)
>>>> +{
>>>> +	struct drm_client_funcs_entry *funcs_entry;
>>>> +	struct drm_client_dev *client, *tmp;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +
>>>> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
>>>> +		if (client->funcs == funcs)
>>>> +			drm_client_remove(client);
>>>> +	}
>>>> +
>>>> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
>>>> +		if (funcs_entry->funcs == funcs) {
>>>> +			list_del(&funcs_entry->list);
>>>> +			kfree(funcs_entry);
>>>> +			break;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +
>>>> +	DRM_DEBUG_KMS("%s\n", funcs->name);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_unregister);
>>>> +
>>>> +void drm_client_dev_register(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_funcs_entry *funcs_entry;
>>>> +
>>>> +	/*
>>>> +	 * Minors are created at the beginning of drm_dev_register(), but can
>>>> +	 * be removed again if the function fails. Since we iterate DRM devices
>>>> +	 * by walking DRM minors, we need to stay under this lock.
>>>> +	 */
>>>> +	lockdep_assert_held(&drm_global_mutex);
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
>>>> +		drm_client_new(dev, funcs_entry->funcs);
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +void drm_client_dev_unregister(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_dev *client, *tmp;
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry_safe(client, tmp, &drm_client_list, list)
>>>> +		if (client->dev == dev)
>>>> +			drm_client_remove(client);
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +void drm_client_dev_hotplug(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_dev *client;
>>>> +	int ret;
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry(client, &drm_client_list, list)
>>>> +		if (client->dev == dev && client->funcs->hotplug) {
>>>> +			ret = client->funcs->hotplug(client);
>>>> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
>>>> +					  client->funcs->name, ret);
>>>> +		}
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +void drm_client_dev_lastclose(struct drm_device *dev)
>>>> +{
>>>> +	struct drm_client_dev *client;
>>>> +	int ret;
>>>> +
>>>> +	if (!drm_core_check_feature(dev, DRIVER_MODESET))
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&drm_client_list_lock);
>>>> +	list_for_each_entry(client, &drm_client_list, list)
>>>> +		if (client->dev == dev && client->funcs->lastclose) {
>>>> +			ret = client->funcs->lastclose(client);
>>>> +			DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
>>>> +					  client->funcs->name, ret);
>>>> +		}
>>>> +	mutex_unlock(&drm_client_list_lock);
>>>> +}
>>>> +
>>>> +/* Get static info */
>>>> +static int drm_client_init(struct drm_client_dev *client, struct drm_file *file)
>>>> +{
>>>> +	struct drm_mode_card_res card_res = {};
>>>> +	struct drm_device *dev = client->dev;
>>>> +	u32 *crtcs;
>>>> +	int ret;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, file, false);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +	if (!card_res.count_crtcs)
>>>> +		return -ENOENT;
>>>> +
>>>> +	crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
>>>> +	if (!crtcs)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	card_res.count_fbs = 0;
>>>> +	card_res.count_connectors = 0;
>>>> +	card_res.count_encoders = 0;
>>>> +	card_res.crtc_id_ptr = (unsigned long)crtcs;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, file, false);
>>>> +	if (ret) {
>>>> +		kfree(crtcs);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	client->crtcs = crtcs;
>>>> +	client->num_crtcs = card_res.count_crtcs;
>>>> +	client->min_width = card_res.min_width;
>>>> +	client->max_width = card_res.max_width;
>>>> +	client->min_height = card_res.min_height;
>>>> +	client->max_height = card_res.max_height;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_get_file - Get a DRM file
>>>> + * @client: DRM client
>>>> + *
>>>> + * This function makes sure the client has a &drm_file available. The client
>>>> + * doesn't normally need to call this, since all client functions that depends
>>>> + * on a DRM file will call it. A matching call to drm_client_put_file() is
>>>> + * necessary.
>>>> + *
>>>> + * The reason for not opening a DRM file when a @client is created is because
>>>> + * we have to take a ref on the driver module due to &drm_driver->postclose
>>>> + * being called in drm_file_free(). Having a DRM file open for the lifetime of
>>>> + * the client instance would block driver module unload.
>>>> + *
>>>> + * Returns:
>>>> + * Zero on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_get_file(struct drm_client_dev *client)
>>>> +{
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_file *file;
>>>> +	int ret = 0;
>>>> +
>>>> +	mutex_lock(&client->lock);
>>>> +
>>>> +	if (client->file_ref_count++) {
>>>> +		mutex_unlock(&client->lock);
>>>> +		return 0;
>>>> +	}
>>>> +
>>>> +	if (!try_module_get(dev->driver->fops->owner)) {
>>>> +		ret = -ENODEV;
>>>> +		goto err_unlock;
>>>> +	}
>>>> +
>>>> +	drm_dev_get(dev);
>>>> +
>>>> +	file = drm_file_alloc(dev->primary);
>>>> +	if (IS_ERR(file)) {
>>>> +		ret = PTR_ERR(file);
>>>> +		goto err_put;
>>>> +	}
>>>> +
>>>> +	if (!client->crtcs) {
>>>> +		ret = drm_client_init(client, file);
>>>> +		if (ret)
>>>> +			goto err_free;
>>>> +	}
>>>> +
>>>> +	mutex_lock(&dev->filelist_mutex);
>>>> +	list_add(&file->lhead, &dev->filelist_internal);
>>>> +	mutex_unlock(&dev->filelist_mutex);
>>>> +
>>>> +	client->file = file;
>>>> +
>>>> +	mutex_unlock(&client->lock);
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_free:
>>>> +	drm_file_free(file);
>>>> +err_put:
>>>> +	drm_dev_put(dev);
>>>> +	module_put(dev->driver->fops->owner);
>>>> +err_unlock:
>>>> +	client->file_ref_count = 0;
>>>> +	mutex_unlock(&client->lock);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_get_file);
>>>> +
>>>> +void drm_client_put_file(struct drm_client_dev *client)
>>>> +{
>>>> +	struct drm_device *dev = client->dev;
>>>> +
>>>> +	if (!client)
>>>> +		return;
>>>> +
>>>> +	mutex_lock(&client->lock);
>>>> +
>>>> +	if (WARN_ON(!client->file_ref_count))
>>>> +		goto out_unlock;
>>>> +
>>>> +	if (--client->file_ref_count)
>>>> +		goto out_unlock;
>>>> +
>>>> +	mutex_lock(&dev->filelist_mutex);
>>>> +	list_del(&client->file->lhead);
>>>> +	mutex_unlock(&dev->filelist_mutex);
>>>> +
>>>> +	drm_file_free(client->file);
>>>> +	client->file = NULL;
>>>> +	drm_dev_put(dev);
>>>> +	module_put(dev->driver->fops->owner);
>>>> +out_unlock:
>>>> +	mutex_unlock(&client->lock);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_put_file);
>>>> +
>>>> +static struct drm_pending_event *
>>>> +drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file *file)
>>>> +{
>>>> +	struct drm_pending_event *e = NULL;
>>>> +	int ret;
>>>> +
>>>> +	ret = mutex_lock_interruptible(&file->event_read_lock);
>>>> +	if (ret)
>>>> +		return ERR_PTR(ret);
>>>> +
>>>> +	spin_lock_irq(&dev->event_lock);
>>>> +	if (!list_empty(&file->event_list)) {
>>>> +		e = list_first_entry(&file->event_list,
>>>> +				     struct drm_pending_event, link);
>>>> +		file->event_space += e->event->length;
>>>> +		list_del(&e->link);
>>>> +	}
>>>> +	spin_unlock_irq(&dev->event_lock);
>>>> +
>>>> +	mutex_unlock(&file->event_read_lock);
>>>> +
>>>> +	return e;
>>>> +}
>>>> +
>>>> +struct drm_event *
>>>> +drm_client_read_event(struct drm_client_dev *client, bool block)
>>>> +{
>>>> +	struct drm_file *file = client->file;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_pending_event *e;
>>>> +	struct drm_event *event;
>>>> +	int ret;
>>>> +
>>>> +	/* Allocate so it fits all events, there's a sanity check later */
>>>> +	event = kzalloc(128, GFP_KERNEL);
>>>> +	if (!event)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	e = drm_client_read_get_pending_event(dev, file);
>>>> +	if (IS_ERR(e)) {
>>>> +		ret = PTR_ERR(e);
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	if (!e && !block) {
>>>> +		ret = 0;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	ret = wait_event_interruptible_timeout(file->event_wait,
>>>> +					       !list_empty(&file->event_list),
>>>> +					       5 * HZ);
>>>> +	if (!ret)
>>>> +		ret = -ETIMEDOUT;
>>>> +	if (ret < 0)
>>>> +		goto err_free;
>>>> +
>>>> +	e = drm_client_read_get_pending_event(dev, file);
>>>> +	if (IS_ERR_OR_NULL(e)) {
>>>> +		ret = PTR_ERR_OR_ZERO(e);
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	if (WARN_ON(e->event->length > 128)) {
>>>> +		/* Increase buffer if this happens */
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	memcpy(event, e->event, e->event->length);
>>>> +	kfree(e);
>>>> +
>>>> +	return event;
>>>> +
>>>> +err_free:
>>>> +	kfree(event);
>>>> +
>>>> +	return ret ? ERR_PTR(ret) : NULL;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_read_event);
>>>> +
>>>> +static void drm_client_connector_free(struct drm_client_connector *connector)
>>>> +{
>>>> +	if (!connector)
>>>> +		return;
>>>> +	kfree(connector->modes);
>>>> +	kfree(connector);
>>>> +}
>>>> +
>>>> +static struct drm_client_connector *
>>>> +drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
>>>> +{
>>>> +	struct drm_mode_get_connector req = {
>>>> +		.connector_id = id,
>>>> +	};
>>>> +	struct drm_client_connector *connector;
>>>> +	struct drm_mode_modeinfo *modes = NULL;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_connector *conn;
>>>> +	bool non_desktop;
>>>> +	int ret;
>>>> +
>>>> +	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
>>>> +	if (!connector)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	connector->conn_id = id;
>>>> +	connector->status = req.connection;
>>>> +
>>>> +	conn = drm_connector_lookup(dev, client->file, id);
>>>> +	if (!conn) {
>>>> +		ret = -ENOENT;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	non_desktop = conn->display_info.non_desktop;
>>>> +
>>>> +	connector->has_tile = conn->has_tile;
>>>> +	connector->tile_h_loc = conn->tile_h_loc;
>>>> +	connector->tile_v_loc = conn->tile_v_loc;
>>>> +	if (conn->tile_group)
>>>> +		connector->tile_group = conn->tile_group->id;
>>>> +
>>>> +	drm_connector_put(conn);
>>>> +
>>>> +	if (non_desktop) {
>>>> +		kfree(connector);
>>>> +		return NULL;
>>>> +	}
>>>> +
>>>> +	if (!req.count_modes)
>>>> +		return connector;
>>>> +
>>>> +	modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
>>>> +	if (!modes) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	connector->modes = modes;
>>>> +	connector->num_modes = req.count_modes;
>>>> +
>>>> +	req.count_props = 0;
>>>> +	req.count_encoders = 0;
>>>> +	req.modes_ptr = (unsigned long)modes;
>>>> +
>>>> +	ret = drm_mode_getconnector(dev, &req, client->file, false);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	return connector;
>>>> +
>>>> +err_free:
>>>> +	kfree(modes);
>>>> +	kfree(connector);
>>>> +
>>>> +	return ERR_PTR(ret);
>>>> +}
>>>> +
>>>> +static int drm_client_get_connectors(struct drm_client_dev *client,
>>>> +				     struct drm_client_connector ***connectors)
>>>> +{
>>>> +	struct drm_mode_card_res card_res = {};
>>>> +	struct drm_device *dev = client->dev;
>>>> +	int ret, num_connectors;
>>>> +	u32 *connector_ids;
>>>> +	unsigned int i;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +	if (!card_res.count_connectors)
>>>> +		return 0;
>>>> +
>>>> +	num_connectors = card_res.count_connectors;
>>>> +	connector_ids = kcalloc(num_connectors,
>>>> +				sizeof(*connector_ids), GFP_KERNEL);
>>>> +	if (!connector_ids)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	card_res.count_fbs = 0;
>>>> +	card_res.count_crtcs = 0;
>>>> +	card_res.count_encoders = 0;
>>>> +	card_res.connector_id_ptr = (unsigned long)connector_ids;
>>>> +
>>>> +	ret = drm_mode_getresources(dev, &card_res, client->file, false);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	*connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
>>>> +	if (!(*connectors)) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		struct drm_client_connector *connector;
>>>> +
>>>> +		connector = drm_client_get_connector(client, connector_ids[i]);
>>>> +		if (IS_ERR(connector)) {
>>>> +			ret = PTR_ERR(connector);
>>>> +			goto err_free;
>>>> +		}
>>>> +		if (connector)
>>>> +			(*connectors)[i] = connector;
>>>> +		else
>>>> +			num_connectors--;
>>>> +	}
>>>> +
>>>> +	if (!num_connectors) {
>>>> +		ret = 0;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	return num_connectors;
>>>> +
>>>> +err_free:
>>>> +	if (connectors)
>>>> +		for (i = 0; i < num_connectors; i++)
>>>> +			drm_client_connector_free((*connectors)[i]);
>>>> +
>>>> +	kfree(connectors);
>>>> +	kfree(connector_ids);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static bool
>>>> +drm_client_connector_is_enabled(struct drm_client_connector *connector,
>>>> +				bool strict)
>>>> +{
>>>> +	if (strict)
>>>> +		return connector->status == connector_status_connected;
>>>> +	else
>>>> +		return connector->status != connector_status_disconnected;
>>>> +}
>>>> +
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_first_mode(struct drm_client_display *display)
>>>> +{
>>>> +	if (!display->num_modes)
>>>> +		return NULL;
>>>> +	return display->modes;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_first_mode);
>>>> +
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_next_mode(struct drm_client_display *display,
>>>> +			     struct drm_mode_modeinfo *mode)
>>>> +{
>>>> +	struct drm_mode_modeinfo *modes = display->modes;
>>>> +
>>>> +	if (++mode < &modes[display->num_modes])
>>>> +		return mode;
>>>> +
>>>> +	return NULL;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_next_mode);
>>>> +
>>>> +static void
>>>> +drm_client_display_fill_tile_modes(struct drm_client_display *display,
>>>> +				   struct drm_mode_modeinfo *tile_modes)
>>>> +{
>>>> +	unsigned int i, j, num_modes = display->connectors[0]->num_modes;
>>>> +	struct drm_mode_modeinfo *tile_mode, *conn_mode;
>>>> +
>>>> +	if (!num_modes) {
>>>> +		kfree(tile_modes);
>>>> +		kfree(display->modes);
>>>> +		display->modes = NULL;
>>>> +		display->num_modes = 0;
>>>> +		return;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_modes; i++) {
>>>> +		tile_mode = &tile_modes[i];
>>>> +
>>>> +		conn_mode = &display->connectors[0]->modes[i];
>>>> +		tile_mode->clock = conn_mode->clock;
>>>> +		tile_mode->vscan = conn_mode->vscan;
>>>> +		tile_mode->vrefresh = conn_mode->vrefresh;
>>>> +		tile_mode->flags = conn_mode->flags;
>>>> +		tile_mode->type = conn_mode->type;
>>>> +
>>>> +		for (j = 0; j < display->num_connectors; j++) {
>>>> +			conn_mode = &display->connectors[j]->modes[i];
>>>> +
>>>> +			if (!display->connectors[j]->tile_h_loc) {
>>>> +				tile_mode->hdisplay += conn_mode->hdisplay;
>>>> +				tile_mode->hsync_start += conn_mode->hsync_start;
>>>> +				tile_mode->hsync_end += conn_mode->hsync_end;
>>>> +				tile_mode->htotal += conn_mode->htotal;
>>>> +			}
>>>> +
>>>> +			if (!display->connectors[j]->tile_v_loc) {
>>>> +				tile_mode->vdisplay += conn_mode->vdisplay;
>>>> +				tile_mode->vsync_start += conn_mode->vsync_start;
>>>> +				tile_mode->vsync_end += conn_mode->vsync_end;
>>>> +				tile_mode->vtotal += conn_mode->vtotal;
>>>> +			}
>>>> +		}
>>>> +	}
>>>> +
>>>> +	kfree(display->modes);
>>>> +	display->modes = tile_modes;
>>>> +	display->num_modes = num_modes;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_display_update_modes - Fetch display modes
>>>> + * @display: Client display
>>>> + * @mode_changed: Optional pointer to boolen which return whether the modes
>>>> + *                have changed or not.
>>>> + *
>>>> + * This function can be used in the client hotplug callback to check if the
>>>> + * video modes have changed and get them up-to-date.
>>>> + *
>>>> + * Returns:
>>>> + * Number of modes on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_display_update_modes(struct drm_client_display *display,
>>>> +				    bool *mode_changed)
>>>> +{
>>>> +	unsigned int num_connectors = display->num_connectors;
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_mode_modeinfo *display_tile_modes;
>>>> +	struct drm_client_connector **connectors;
>>>> +	unsigned int i, num_modes = 0;
>>>> +	bool dummy_changed = false;
>>>> +	int ret;
>>>> +
>>>> +	if (mode_changed)
>>>> +		*mode_changed = false;
>>>> +	else
>>>> +		mode_changed = &dummy_changed;
>>>> +
>>>> +	if (display->cloned)
>>>> +		return 2;
>>>> +
>>>> +	ret = drm_client_get_file(client);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
>>>> +	if (!connectors) {
>>>> +		ret = -ENOMEM;
>>>> +		goto out_put_file;
>>>> +	}
>>>> +
>>>> +	/* Get a new set for comparison */
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		connectors[i] = drm_client_get_connector(client, display->connectors[i]->conn_id);
>>>> +		if (IS_ERR_OR_NULL(connectors[i])) {
>>>> +			ret = PTR_ERR_OR_ZERO(connectors[i]);
>>>> +			if (!ret)
>>>> +				ret = -ENOENT;
>>>> +			goto out_cleanup;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	/* All connectors should have the same number of modes */
>>>> +	num_modes = connectors[0]->num_modes;
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		if (num_modes != connectors[i]->num_modes) {
>>>> +			ret = -EINVAL;
>>>> +			goto out_cleanup;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	if (num_connectors > 1) {
>>>> +		display_tile_modes = kcalloc(num_modes, sizeof(*display_tile_modes), GFP_KERNEL);
>>>> +		if (!display_tile_modes) {
>>>> +			ret = -ENOMEM;
>>>> +			goto out_cleanup;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	mutex_lock(&display->modes_lock);
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		display->connectors[i]->status = connectors[i]->status;
>>>> +		if (display->connectors[i]->num_modes != connectors[i]->num_modes) {
>>>> +			display->connectors[i]->num_modes = connectors[i]->num_modes;
>>>> +			kfree(display->connectors[i]->modes);
>>>> +			display->connectors[i]->modes = connectors[i]->modes;
>>>> +			connectors[i]->modes = NULL;
>>>> +			*mode_changed = true;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	if (num_connectors > 1)
>>>> +		drm_client_display_fill_tile_modes(display, display_tile_modes);
>>>> +	else
>>>> +		display->modes = display->connectors[0]->modes;
>>>> +
>>>> +	mutex_unlock(&display->modes_lock);
>>>> +
>>>> +out_cleanup:
>>>> +	for (i = 0; i < num_connectors; i++)
>>>> +		drm_client_connector_free(connectors[i]);
>>>> +	kfree(connectors);
>>>> +out_put_file:
>>>> +	drm_client_put_file(client);
>>>> +
>>>> +	return ret ? ret : num_modes;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_update_modes);
>>>> +
>>>> +void drm_client_display_free(struct drm_client_display *display)
>>>> +{
>>>> +	unsigned int i;
>>>> +
>>>> +	if (!display)
>>>> +		return;
>>>> +
>>>> +	/* tile modes? */
>>>> +	if (display->modes != display->connectors[0]->modes)
>>>> +		kfree(display->modes);
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++)
>>>> +		drm_client_connector_free(display->connectors[i]);
>>>> +
>>>> +	kfree(display->connectors);
>>>> +	mutex_destroy(&display->modes_lock);
>>>> +	kfree(display);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_free);
>>>> +
>>>> +static struct drm_client_display *
>>>> +drm_client_display_alloc(struct drm_client_dev *client,
>>>> +			 unsigned int num_connectors)
>>>> +{
>>>> +	struct drm_client_display *display;
>>>> +	struct drm_client_connector **connectors;
>>>> +
>>>> +	display = kzalloc(sizeof(*display), GFP_KERNEL);
>>>> +	connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
>>>> +	if (!display || !connectors) {
>>>> +		kfree(display);
>>>> +		kfree(connectors);
>>>> +		return NULL;
>>>> +	}
>>>> +
>>>> +	mutex_init(&display->modes_lock);
>>>> +	display->client = client;
>>>> +	display->connectors = connectors;
>>>> +	display->num_connectors = num_connectors;
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +/* Logic is from drm_fb_helper */
>>>> +static struct drm_client_display *
>>>> +drm_client_connector_pick_cloned(struct drm_client_dev *client,
>>>> +				 struct drm_client_connector **connectors,
>>>> +				 unsigned int num_connectors)
>>>> +{
>>>> +	struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
>>>> +	struct drm_display_mode *dmt_display_mode = NULL;
>>>> +	unsigned int i, j, conns[2], num_conns = 0;
>>>> +	struct drm_client_connector *connector;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_client_display *display;
>>>> +
>>>> +	/* only contemplate cloning in the single crtc case */
>>>> +	if (dev->mode_config.num_crtc > 1)
>>>> +		return NULL;
>>>> +retry:
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		connector = connectors[i];
>>>> +		if (!connector || connector->has_tile || !connector->num_modes)
>>>> +			continue;
>>>> +
>>>> +		for (j = 0; j < connector->num_modes; j++) {
>>>> +			mode = &connector->modes[j];
>>>> +			if (dmt_display_mode) {
>>>> +				if (drm_umode_equal(&udmt_mode, mode)) {
>>>> +					conns[num_conns] = i;
>>>> +					modes[num_conns++] = *mode;
>>>> +					break;
>>>> +				}
>>>> +			} else {
>>>> +				if (mode->type & DRM_MODE_TYPE_USERDEF) {
>>>> +					conns[num_conns] = i;
>>>> +					modes[num_conns++] = *mode;
>>>> +					break;
>>>> +				}
>>>> +			}
>>>> +		}
>>>> +		if (num_conns == 2)
>>>> +			break;
>>>> +	}
>>>> +
>>>> +	if (num_conns == 2)
>>>> +		goto found;
>>>> +
>>>> +	if (dmt_display_mode)
>>>> +		return NULL;
>>>> +
>>>> +	dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
>>>> +	drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
>>>> +	drm_mode_destroy(dev, dmt_display_mode);
>>>> +
>>>> +	goto retry;
>>>> +
>>>> +found:
>>>> +	tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
>>>> +	if (!tmp)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	display = drm_client_display_alloc(client, 2);
>>>> +	if (!display) {
>>>> +		kfree(tmp);
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < 2; i++) {
>>>> +		connector = connectors[conns[i]];
>>>> +		display->connectors[i] = connector;
>>>> +		connectors[conns[i]] = NULL;
>>>> +		kfree(connector->modes);
>>>> +		tmp[i] = modes[i];
>>>> +		connector->modes = &tmp[i];
>>>> +		connector->num_modes = 1;
>>>> +	}
>>>> +
>>>> +	display->cloned = true;
>>>> +	display->modes = &tmp[0];
>>>> +	display->num_modes = 1;
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +static struct drm_client_display *
>>>> +drm_client_connector_pick_tile(struct drm_client_dev *client,
>>>> +			       struct drm_client_connector **connectors,
>>>> +			       unsigned int num_connectors)
>>>> +{
>>>> +	unsigned int i, num_conns, num_modes, tile_group = 0;
>>>> +	struct drm_mode_modeinfo *tile_modes = NULL;
>>>> +	struct drm_client_connector *connector;
>>>> +	struct drm_client_display *display;
>>>> +	u16 conns[32];
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		connector = connectors[i];
>>>> +		if (!connector || !connector->tile_group)
>>>> +			continue;
>>>> +
>>>> +		if (!tile_group) {
>>>> +			tile_group = connector->tile_group;
>>>> +			num_modes = connector->num_modes;
>>>> +		}
>>>> +
>>>> +		if (connector->tile_group != tile_group)
>>>> +			continue;
>>>> +
>>>> +		if (num_modes != connector->num_modes) {
>>>> +			DRM_ERROR("Tile connectors must have the same number of modes\n");
>>>> +			return ERR_PTR(-EINVAL);
>>>> +		}
>>>> +
>>>> +		conns[num_conns++] = i;
>>>> +		if (WARN_ON(num_conns == 33))
>>>> +			return ERR_PTR(-EINVAL);
>>>> +	}
>>>> +
>>>> +	if (!num_conns)
>>>> +		return NULL;
>>>> +
>>>> +	if (num_modes) {
>>>> +		tile_modes = kcalloc(num_modes, sizeof(*tile_modes), GFP_KERNEL);
>>>> +		if (!tile_modes)
>>>> +			return ERR_PTR(-ENOMEM);
>>>> +	}
>>>> +
>>>> +	display = drm_client_display_alloc(client, num_conns);
>>>> +	if (!display) {
>>>> +		kfree(tile_modes);
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +	}
>>>> +
>>>> +	if (num_modes)
>>>> +		drm_client_display_fill_tile_modes(display, tile_modes);
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +static struct drm_client_display *
>>>> +drm_client_connector_pick_not_tile(struct drm_client_dev *client,
>>>> +				   struct drm_client_connector **connectors,
>>>> +				   unsigned int num_connectors)
>>>> +{
>>>> +	struct drm_client_display *display;
>>>> +	unsigned int i;
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		if (!connectors[i] || connectors[i]->has_tile)
>>>> +			continue;
>>>> +		break;
>>>> +	}
>>>> +
>>>> +	if (i == num_connectors)
>>>> +		return NULL;
>>>> +
>>>> +	display = drm_client_display_alloc(client, 1);
>>>> +	if (!display)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	display->connectors[0] = connectors[i];
>>>> +	connectors[i] = NULL;
>>>> +	display->modes = display->connectors[0]->modes;
>>>> +	display->num_modes = display->connectors[0]->num_modes;
>>>> +
>>>> +	return display;
>>>> +}
>>>> +
>>>> +/* Get connectors and bundle them up into displays */
>>>> +static int drm_client_get_displays(struct drm_client_dev *client,
>>>> +				   struct drm_client_display ***displays)
>>>> +{
>>>> +	int ret, num_connectors, num_displays = 0;
>>>> +	struct drm_client_connector **connectors;
>>>> +	struct drm_client_display *display;
>>>> +	unsigned int i;
>>>> +
>>>> +	ret = drm_client_get_file(client);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	num_connectors = drm_client_get_connectors(client, &connectors);
>>>> +	if (num_connectors <= 0) {
>>>> +		ret = num_connectors;
>>>> +		goto err_put_file;
>>>> +	}
>>>> +
>>>> +	*displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
>>>> +	if (!(*displays)) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_free;
>>>> +	}
>>>> +
>>>> +	display = drm_client_connector_pick_cloned(client, connectors,
>>>> +						   num_connectors);
>>>> +	if (IS_ERR(display)) {
>>>> +		ret = PTR_ERR(display);
>>>> +		goto err_free;
>>>> +	}
>>>> +	if (display)
>>>> +		(*displays)[num_displays++] = display;
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		display = drm_client_connector_pick_tile(client, connectors,
>>>> +							 num_connectors);
>>>> +		if (IS_ERR(display)) {
>>>> +			ret = PTR_ERR(display);
>>>> +			goto err_free;
>>>> +		}
>>>> +		if (!display)
>>>> +			break;
>>>> +		(*displays)[num_displays++] = display;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		display = drm_client_connector_pick_not_tile(client, connectors,
>>>> +							     num_connectors);
>>>> +		if (IS_ERR(display)) {
>>>> +			ret = PTR_ERR(display);
>>>> +			goto err_free;
>>>> +		}
>>>> +		if (!display)
>>>> +			break;
>>>> +		(*displays)[num_displays++] = display;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_connectors; i++) {
>>>> +		if (connectors[i]) {
>>>> +			DRM_INFO("Connector %u fell through the cracks.\n",
>>>> +				 connectors[i]->conn_id);
>>>> +			drm_client_connector_free(connectors[i]);
>>>> +		}
>>>> +	}
>>>> +
>>>> +	drm_client_put_file(client);
>>>> +	kfree(connectors);
>>>> +
>>>> +	return num_displays;
>>>> +
>>>> +err_free:
>>>> +	for (i = 0; i < num_displays; i++)
>>>> +		drm_client_display_free((*displays)[i]);
>>>> +	kfree(*displays);
>>>> +	*displays = NULL;
>>>> +	for (i = 0; i < num_connectors; i++)
>>>> +		drm_client_connector_free(connectors[i]);
>>>> +	kfree(connectors);
>>>> +err_put_file:
>>>> +	drm_client_put_file(client);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static bool
>>>> +drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
>>>> +{
>>>> +	unsigned int i;
>>>> +
>>>> +	if (!display->num_modes)
>>>> +		return false;
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++)
>>>> +		if (!drm_client_connector_is_enabled(display->connectors[i], strict))
>>>> +			return false;
>>>> +
>>>> +	return true;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_display_get_first_enabled - Get first enabled display
>>>> + * @client: DRM client
>>>> + * @strict: If true the connector(s) have to be connected, if false they can
>>>> + *          also have unknown status.
>>>> + *
>>>> + * This function gets all connectors and bundles them into displays
>>>> + * (tiled/cloned). It then picks the first one with connectors that is enabled
>>>> + * according to @strict.
>>>> + *
>>>> + * Returns:
>>>> + * Pointer to a client display if such a display was found, NULL if not found
>>>> + * or an error pointer on failure.
>>>> + */
>>>> +struct drm_client_display *
>>>> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict)
>>>> +{
>>>> +	struct drm_client_display **displays, *display = NULL;
>>>> +	int num_displays;
>>>> +	unsigned int i;
>>>> +
>>>> +	num_displays = drm_client_get_displays(client, &displays);
>>>> +	if (num_displays < 0)
>>>> +		return ERR_PTR(num_displays);
>>>> +	if (!num_displays)
>>>> +		return NULL;
>>>> +
>>>> +	for (i = 0; i < num_displays; i++) {
>>>> +		if (!display &&
>>>> +		    drm_client_display_is_enabled(displays[i], strict)) {
>>>> +			display = displays[i];
>>>> +			continue;
>>>> +		}
>>>> +		drm_client_display_free(displays[i]);
>>>> +	}
>>>> +
>>>> +	kfree(displays);
>>>> +
>>>> +	return display;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_get_first_enabled);
>>>> +
>>>> +unsigned int
>>>> +drm_client_display_preferred_depth(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_connector *conn;
>>>> +	unsigned int ret;
>>>> +
>>>> +	conn = drm_connector_lookup(display->client->dev, NULL,
>>>> +				    display->connectors[0]->conn_id);
>>>> +	if (!conn)
>>>> +		return 0;
>>>> +
>>>> +	if (conn->cmdline_mode.bpp_specified)
>>>> +		ret = conn->cmdline_mode.bpp;
>>>> +	else
>>>> +		ret = display->client->dev->mode_config.preferred_depth;
>>>> +
>>>> +	drm_connector_put(conn);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_preferred_depth);
>>>> +
>>>> +int drm_client_display_dpms(struct drm_client_display *display, int mode)
>>>> +{
>>>> +	struct drm_mode_obj_set_property prop;
>>>> +
>>>> +	prop.value = mode;
>>>> +	prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
>>>> +	prop.obj_id = display->connectors[0]->conn_id;
>>>> +	prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
>>>> +
>>>> +	return drm_mode_obj_set_property(display->client->dev, &prop,
>>>> +					 display->client->file);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_dpms);
>>>> +
>>>> +int drm_client_display_wait_vblank(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_crtc *crtc;
>>>> +	union drm_wait_vblank vblank_req = {
>>>> +		.request = {
>>>> +			.type = _DRM_VBLANK_RELATIVE,
>>>> +			.sequence = 1,
>>>> +		},
>>>> +	};
>>>> +
>>>> +	crtc = drm_crtc_find(display->client->dev, display->client->file,
>>>> +			     display->connectors[0]->crtc_id);
>>>> +	if (!crtc)
>>>> +		return -ENOENT;
>>>> +
>>>> +	vblank_req.request.type |= drm_crtc_index(crtc) << _DRM_VBLANK_HIGH_CRTC_SHIFT;
>>>> +
>>>> +	return drm_wait_vblank(display->client->dev, &vblank_req,
>>>> +			       display->client->file);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_wait_vblank);
>>>> +
>>>> +static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
>>>> +{
>>>> +	int i;
>>>> +
>>>> +	for (i = 0; i < client->num_crtcs; i++)
>>>> +		if (client->crtcs[i] == id)
>>>> +			return i;
>>>> +
>>>> +	return -ENOENT;
>>>> +}
>>>> +
>>>> +static int drm_client_display_find_crtcs(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	struct drm_file *file = client->file;
>>>> +	u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
>>>> +	unsigned int i, j, available_crtcs = ~0;
>>>> +	struct drm_mode_get_connector conn_req;
>>>> +	struct drm_mode_get_encoder enc_req;
>>>> +	int ret;
>>>> +
>>>> +	/* Already done? */
>>>> +	if (display->connectors[0]->crtc_id)
>>>> +		return 0;
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++) {
>>>> +		u32 active_crtcs = 0, crtcs_for_connector = 0;
>>>> +		int crtc_idx;
>>>> +
>>>> +		memset(&conn_req, 0, sizeof(conn_req));
>>>> +		conn_req.connector_id = display->connectors[i]->conn_id;
>>>> +		conn_req.encoders_ptr = (unsigned long)(encoder_ids);
>>>> +		conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
>>>> +		ret = drm_mode_getconnector(dev, &conn_req, file, false);
>>>> +		if (ret)
>>>> +			return ret;
>>>> +
>>>> +		if (conn_req.encoder_id) {
>>>> +			memset(&enc_req, 0, sizeof(enc_req));
>>>> +			enc_req.encoder_id = conn_req.encoder_id;
>>>> +			ret = drm_mode_getencoder(dev, &enc_req, file);
>>>> +			if (ret)
>>>> +				return ret;
>>>> +			crtcs_for_connector |= enc_req.possible_crtcs;
>>>> +			if (crtcs_for_connector & available_crtcs)
>>>> +				goto found;
>>>> +		}
>>>> +
>>>> +		for (j = 0; j < conn_req.count_encoders; j++) {
>>>> +			memset(&enc_req, 0, sizeof(enc_req));
>>>> +			enc_req.encoder_id = encoder_ids[j];
>>>> +			ret = drm_mode_getencoder(dev, &enc_req, file);
>>>> +			if (ret)
>>>> +				return ret;
>>>> +
>>>> +			crtcs_for_connector |= enc_req.possible_crtcs;
>>>> +
>>>> +			if (enc_req.crtc_id) {
>>>> +				crtc_idx = drm_client_get_crtc_index(client, enc_req.crtc_id);
>>>> +				if (crtc_idx >= 0)
>>>> +					active_crtcs |= 1 << crtc_idx;
>>>> +			}
>>>> +		}
>>>> +
>>>> +found:
>>>> +		crtcs_for_connector &= available_crtcs;
>>>> +		active_crtcs &= available_crtcs;
>>>> +
>>>> +		if (!crtcs_for_connector)
>>>> +			return -ENOENT;
>>>> +
>>>> +		if (active_crtcs)
>>>> +			crtc_idx = ffs(active_crtcs) - 1;
>>>> +		else
>>>> +			crtc_idx = ffs(crtcs_for_connector) - 1;
>>>> +
>>>> +		if (crtc_idx >= client->num_crtcs)
>>>> +			return -EINVAL;
>>>> +
>>>> +		display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
>>>> +		available_crtcs &= ~BIT(crtc_idx);
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_client_display_commit_mode - Commit a mode to the crtc(s)
>>>> + * @display: Client display
>>>> + * @fb_id: Framebuffer id
>>>> + * @mode: Video mode
>>>> + *
>>>> + * Returns:
>>>> + * Zero on success, negative error code on failure.
>>>> + */
>>>> +int drm_client_display_commit_mode(struct drm_client_display *display,
>>>> +				   u32 fb_id, struct drm_mode_modeinfo *mode)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_device *dev = client->dev;
>>>> +	unsigned int num_crtcs = client->num_crtcs;
>>>> +	struct drm_file *file = client->file;
>>>> +	unsigned int *xoffsets = NULL, *yoffsets = NULL;
>>>> +	struct drm_mode_crtc *crtc_reqs, *req;
>>>> +	u32 cloned_conn_ids[2];
>>>> +	unsigned int i;
>>>> +	int idx, ret;
>>>> +
>>>> +	ret = drm_client_display_find_crtcs(display);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
>>>> +	if (!crtc_reqs)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	for (i = 0; i < num_crtcs; i++)
>>>> +		crtc_reqs[i].crtc_id = client->crtcs[i];
>>>> +
>>>> +	if (drm_client_display_is_tiled(display)) {
>>>> +		/* TODO calculate tile crtc offsets */
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < display->num_connectors; i++) {
>>>> +		idx = drm_client_get_crtc_index(client, display->connectors[i]->crtc_id);
>>>> +		if (idx < 0)
>>>> +			return -ENOENT;
>>>> +
>>>> +		req = &crtc_reqs[idx];
>>>> +
>>>> +		req->fb_id = fb_id;
>>>> +		if (xoffsets) {
>>>> +			req->x = xoffsets[i];
>>>> +			req->y = yoffsets[i];
>>>> +		}
>>>> +		req->mode_valid = 1;
>>>> +		req->mode = *mode;
>>>> +
>>>> +		if (display->cloned) {
>>>> +			cloned_conn_ids[0] = display->connectors[0]->conn_id;
>>>> +			cloned_conn_ids[1] = display->connectors[1]->conn_id;
>>>> +			req->set_connectors_ptr = (unsigned long)(cloned_conn_ids);
>>>> +			req->count_connectors = 2;
>>>> +			break;
>>>> +		}
>>>> +
>>>> +		req->set_connectors_ptr = (unsigned long)(&display->connectors[i]->conn_id);
>>>> +		req->count_connectors = 1;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < num_crtcs; i++) {
>>>> +		ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
>>>> +		if (ret)
>>>> +			break;
>>>> +	}
>>>> +
>>>> +	kfree(xoffsets);
>>>> +	kfree(yoffsets);
>>>> +	kfree(crtc_reqs);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_commit_mode);
>>>> +
>>>> +unsigned int drm_client_display_current_fb(struct drm_client_display *display)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	int ret;
>>>> +	struct drm_mode_crtc crtc_req = {
>>>> +		.crtc_id = display->connectors[0]->crtc_id,
>>>> +	};
>>>> +
>>>> +	ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
>>>> +	if (ret)
>>>> +		return 0;
>>>> +
>>>> +	return crtc_req.fb_id;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_current_fb);
>>>> +
>>>> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
>>>> +			     struct drm_clip_rect *clips, unsigned int num_clips)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_mode_fb_dirty_cmd dirty_req = {
>>>> +		.fb_id = fb_id,
>>>> +		.clips_ptr = (unsigned long)clips,
>>>> +		.num_clips = num_clips,
>>>> +	};
>>>> +	int ret;
>>>> +
>>>> +	if (display->no_flushing)
>>>> +		return 0;
>>>> +
>>>> +	ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
>>>> +	if (ret == -ENOSYS) {
>>>> +		ret = 0;
>>>> +		display->no_flushing = true;
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_flush);
>>>> +
>>>> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
>>>> +				 bool event)
>>>> +{
>>>> +	struct drm_client_dev *client = display->client;
>>>> +	struct drm_mode_crtc_page_flip_target page_flip_req = {
>>>> +		.crtc_id = display->connectors[0]->crtc_id,
>>>> +		.fb_id = fb_id,
>>>> +	};
>>>> +
>>>> +	if (event)
>>>> +		page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
>>>> +
>>>> +	return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
>>>> +	/*
>>>> +	 * TODO:
>>>> +	 * Where do we flush on page flip? Should the driver handle that?
>>>> +	 */
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_display_page_flip);
>>>> +
>>>> +/**
>>>> + * drm_client_framebuffer_create - Create a client framebuffer
>>>> + * @client: DRM client
>>>> + * @mode: Display mode to create a buffer for
>>>> + * @format: Buffer format
>>>> + *
>>>> + * This function creates a &drm_client_buffer which consists of a
>>>> + * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
>>>> + * exported to aquire a virtual address which is stored in
>>>> + * &drm_client_buffer->vaddr.
>>>> + * Call drm_client_framebuffer_delete() to free the buffer.
>>>> + *
>>>> + * Returns:
>>>> + * Pointer to a client buffer or an error pointer on failure.
>>>> + */
>>>> +struct drm_client_buffer *
>>>> +drm_client_framebuffer_create(struct drm_client_dev *client,
>>>> +			      struct drm_mode_modeinfo *mode, u32 format)
>>>> +{
>>>> +	struct drm_client_buffer *buffer;
>>>> +	int ret;
>>>> +
>>>> +	buffer = drm_client_buffer_create(client, mode->hdisplay,
>>>> +					  mode->vdisplay, format);
>>>> +	if (IS_ERR(buffer))
>>>> +		return buffer;
>>>> +
>>>> +	ret = drm_client_buffer_addfb(buffer, mode);
>>>> +	if (ret) {
>>>> +		drm_client_buffer_delete(buffer);
>>>> +		return ERR_PTR(ret);
>>>> +	}
>>>> +
>>>> +	return buffer;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_framebuffer_create);
>>>> +
>>>> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
>>>> +{
>>>> +	drm_client_buffer_rmfb(buffer);
>>>> +	drm_client_buffer_delete(buffer);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_framebuffer_delete);
>>>> +
>>>> +struct drm_client_buffer *
>>>> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
>>>> +			 u32 format)
>>>> +{
>>>> +	struct drm_mode_create_dumb dumb_args = { 0 };
>>>> +	struct drm_prime_handle prime_args = { 0 };
>>>> +	struct drm_client_buffer *buffer;
>>>> +	struct dma_buf *dma_buf;
>>>> +	void *vaddr;
>>>> +	int ret;
>>>> +
>>>> +	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
>>>> +	if (!buffer)
>>>> +		return ERR_PTR(-ENOMEM);
>>>> +
>>>> +	ret = drm_client_get_file(client);
>>>> +	if (ret)
>>>> +		goto err_free;
>>>> +
>>>> +	buffer->client = client;
>>>> +	buffer->width = width;
>>>> +	buffer->height = height;
>>>> +	buffer->format = format;
>>>> +
>>>> +	dumb_args.width = buffer->width;
>>>> +	dumb_args.height = buffer->height;
>>>> +	dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
>>>> +	ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
>>>> +	if (ret)
>>>> +		goto err_put_file;
>>>> +
>>>> +	buffer->handle = dumb_args.handle;
>>>> +	buffer->pitch = dumb_args.pitch;
>>>> +	buffer->size = dumb_args.size;
>>>> +
>>>> +	prime_args.handle = dumb_args.handle;
>>>> +	ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
>>>> +	if (ret)
>>>> +		goto err_delete;
>>>> +
>>>> +	dma_buf = dma_buf_get(prime_args.fd);
>>>> +	if (IS_ERR(dma_buf)) {
>>>> +		ret = PTR_ERR(dma_buf);
>>>> +		goto err_delete;
>>>> +	}
>>>> +
>>>> +	buffer->dma_buf = dma_buf;
>>>> +
>>>> +	vaddr = dma_buf_vmap(dma_buf);
>>>> +	if (!vaddr) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err_delete;
>>>> +	}
>>>> +
>>>> +	buffer->vaddr = vaddr;
>>>> +
>>>> +	return buffer;
>>>> +
>>>> +err_delete:
>>>> +	drm_client_buffer_delete(buffer);
>>>> +err_put_file:
>>>> +	drm_client_put_file(client);
>>>> +err_free:
>>>> +	kfree(buffer);
>>>> +
>>>> +	return ERR_PTR(ret);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_create);
>>>> +
>>>> +void drm_client_buffer_delete(struct drm_client_buffer *buffer)
>>>> +{
>>>> +	if (!buffer)
>>>> +		return;
>>>> +
>>>> +	if (buffer->vaddr)
>>>> +		dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
>>>> +
>>>> +	if (buffer->dma_buf)
>>>> +		dma_buf_put(buffer->dma_buf);
>>>> +
>>>> +	drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
>>>> +			      buffer->client->file);
>>>> +	drm_client_put_file(buffer->client);
>>>> +	kfree(buffer);
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_delete);
>>>> +
>>>> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
>>>> +			    struct drm_mode_modeinfo *mode)
>>>> +{
>>>> +	struct drm_client_dev *client = buffer->client;
>>>> +	struct drm_mode_fb_cmd2 fb_req = { };
>>>> +	unsigned int num_fbs, *fb_ids;
>>>> +	int i, ret;
>>>> +
>>>> +	if (buffer->num_fbs)
>>>> +		return -EINVAL;
>>>> +
>>>> +	if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
>>>> +		return -EINVAL;
>>>> +
>>>> +	num_fbs = buffer->height / mode->vdisplay;
>>>> +	fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
>>>> +	if (!fb_ids)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	fb_req.width = mode->hdisplay;
>>>> +	fb_req.height = mode->vdisplay;
>>>> +	fb_req.pixel_format = buffer->format;
>>>> +	fb_req.handles[0] = buffer->handle;
>>>> +	fb_req.pitches[0] = buffer->pitch;
>>>> +
>>>> +	for (i = 0; i < num_fbs; i++) {
>>>> +		fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
>>>> +		ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
>>>> +				      client->funcs->name);
>>>> +		if (ret)
>>>> +			goto err_remove;
>>>> +		fb_ids[i] = fb_req.fb_id;
>>>> +	}
>>>> +
>>>> +	buffer->fb_ids = fb_ids;
>>>> +	buffer->num_fbs = num_fbs;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_remove:
>>>> +	for (i--; i >= 0; i--)
>>>> +		drm_mode_rmfb(client->dev, fb_ids[i], client->file);
>>>> +	kfree(fb_ids);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_addfb);
>>>> +
>>>> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
>>>> +{
>>>> +	unsigned int i;
>>>> +	int ret;
>>>> +
>>>> +	if (!buffer || !buffer->num_fbs)
>>>> +		return 0;
>>>> +
>>>> +	for (i = 0; i < buffer->num_fbs; i++) {
>>>> +		ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
>>>> +				    buffer->client->file);
>>>> +		if (ret)
>>>> +			DRM_DEV_ERROR(buffer->client->dev->dev,
>>>> +				      "Error removing FB:%u (%d)\n",
>>>> +				      buffer->fb_ids[i], ret);
>>>> +	}
>>>> +
>>>> +	kfree(buffer->fb_ids);
>>>> +	buffer->fb_ids = NULL;
>>>> +	buffer->num_fbs = 0;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +EXPORT_SYMBOL(drm_client_buffer_rmfb);
>>>> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
>>>> index f869de185986..db161337d87c 100644
>>>> --- a/drivers/gpu/drm/drm_drv.c
>>>> +++ b/drivers/gpu/drm/drm_drv.c
>>>> @@ -33,6 +33,7 @@
>>>>    #include <linux/mount.h>
>>>>    #include <linux/slab.h>
>>>> +#include <drm/drm_client.h>
>>>>    #include <drm/drm_drv.h>
>>>>    #include <drm/drmP.h>
>>>> @@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
>>>>    	dev->driver = driver;
>>>>    	INIT_LIST_HEAD(&dev->filelist);
>>>> +	INIT_LIST_HEAD(&dev->filelist_internal);
>>>>    	INIT_LIST_HEAD(&dev->ctxlist);
>>>>    	INIT_LIST_HEAD(&dev->vmalist);
>>>>    	INIT_LIST_HEAD(&dev->maplist);
>>>> @@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
>>>>    		 dev->dev ? dev_name(dev->dev) : "virtual device",
>>>>    		 dev->primary->index);
>>>> +	drm_client_dev_register(dev);
>>>> +
>>>>    	goto out_unlock;
>>>>    err_minors:
>>>> @@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
>>>>    	drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
>>>>    	drm_minor_unregister(dev, DRM_MINOR_RENDER);
>>>>    	drm_minor_unregister(dev, DRM_MINOR_CONTROL);
>>>> +
>>>> +	drm_client_dev_unregister(dev);
>>>>    }
>>>>    EXPORT_SYMBOL(drm_dev_unregister);
>>>> diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
>>>> index 55505378df47..bcc688e58776 100644
>>>> --- a/drivers/gpu/drm/drm_file.c
>>>> +++ b/drivers/gpu/drm/drm_file.c
>>>> @@ -35,6 +35,7 @@
>>>>    #include <linux/slab.h>
>>>>    #include <linux/module.h>
>>>> +#include <drm/drm_client.h>
>>>>    #include <drm/drm_file.h>
>>>>    #include <drm/drmP.h>
>>>> @@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
>>>>    	if (drm_core_check_feature(dev, DRIVER_LEGACY))
>>>>    		drm_legacy_dev_reinit(dev);
>>>> +
>>>> +	drm_client_dev_lastclose(dev);
>>>>    }
>>>>    /**
>>>> diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
>>>> index 2d1643bdae78..5d2a6c6717f5 100644
>>>> --- a/drivers/gpu/drm/drm_probe_helper.c
>>>> +++ b/drivers/gpu/drm/drm_probe_helper.c
>>>> @@ -33,6 +33,7 @@
>>>>    #include <linux/moduleparam.h>
>>>>    #include <drm/drmP.h>
>>>> +#include <drm/drm_client.h>
>>>>    #include <drm/drm_crtc.h>
>>>>    #include <drm/drm_fourcc.h>
>>>>    #include <drm/drm_crtc_helper.h>
>>>> @@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
>>>>    	drm_sysfs_hotplug_event(dev);
>>>>    	if (dev->mode_config.funcs->output_poll_changed)
>>>>    		dev->mode_config.funcs->output_poll_changed(dev);
>>>> +
>>>> +	drm_client_dev_hotplug(dev);
>>>>    }
>>>>    EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
>>>> diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
>>>> new file mode 100644
>>>> index 000000000000..88f6f87919c5
>>>> --- /dev/null
>>>> +++ b/include/drm/drm_client.h
>>>> @@ -0,0 +1,192 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>> +
>>>> +#include <linux/mutex.h>
>>>> +
>>>> +struct dma_buf;
>>>> +struct drm_clip_rect;
>>>> +struct drm_device;
>>>> +struct drm_file;
>>>> +struct drm_mode_modeinfo;
>>>> +
>>>> +struct drm_client_dev;
>>>> +
>>>> +/**
>>>> + * struct drm_client_funcs - DRM client  callbacks
>>>> + */
>>>> +struct drm_client_funcs {
>>>> +	/**
>>>> +	 * @name:
>>>> +	 *
>>>> +	 * Name of the client.
>>>> +	 */
>>>> +	const char *name;
>>>> +
>>>> +	/**
>>>> +	 * @new:
>>>> +	 *
>>>> +	 * Called when a client or a &drm_device is registered.
>>>> +	 * If the callback returns anything but zero, then this client instance
>>>> +	 * is dropped.
>>>> +	 *
>>>> +	 * This callback is mandatory.
>>>> +	 */
>>>> +	int (*new)(struct drm_client_dev *client);
>>>> +
>>>> +	/**
>>>> +	 * @remove:
>>>> +	 *
>>>> +	 * Called when a &drm_device is unregistered or the client is
>>>> +	 * unregistered. If zero is returned drm_client_free() is called
>>>> +	 * automatically. If the client can't drop it's resources it should
>>>> +	 * return non-zero and call drm_client_free() later.
>>>> +	 *
>>>> +	 * This callback is optional.
>>>> +	 */
>>>> +	int (*remove)(struct drm_client_dev *client);
>>>> +
>>>> +	/**
>>>> +	 * @lastclose:
>>>> +	 *
>>>> +	 * Called on drm_lastclose(). The first client instance in the list
>>>> +	 * that returns zero gets the privilege to restore and no more clients
>>>> +	 * are called.
>>>> +	 *
>>>> +	 * This callback is optional.
>>>> +	 */
>>>> +	int (*lastclose)(struct drm_client_dev *client);
>>>> +
>>>> +	/**
>>>> +	 * @hotplug:
>>>> +	 *
>>>> +	 * Called on drm_kms_helper_hotplug_event().
>>>> +	 *
>>>> +	 * This callback is optional.
>>>> +	 */
>>>> +	int (*hotplug)(struct drm_client_dev *client);
>>>> +
>>>> +// TODO
>>>> +//	void (*suspend)(struct drm_client_dev *client);
>>>> +//	void (*resume)(struct drm_client_dev *client);
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct drm_client_dev - DRM client instance
>>>> + */
>>>> +struct drm_client_dev {
>>>> +	struct list_head list;
>>>> +	struct drm_device *dev;
>>>> +	const struct drm_client_funcs *funcs;
>>>> +	struct mutex lock;
>>>> +	struct drm_file *file;
>>>> +	unsigned int file_ref_count;
>>>> +	u32 *crtcs;
>>>> +	unsigned int num_crtcs;
>>>> +	u32 min_width;
>>>> +	u32 max_width;
>>>> +	u32 min_height;
>>>> +	u32 max_height;
>>>> +	void *private;
>>>> +};
>>>> +
>>>> +void drm_client_free(struct drm_client_dev *client);
>>>> +int drm_client_register(const struct drm_client_funcs *funcs);
>>>> +void drm_client_unregister(const struct drm_client_funcs *funcs);
>>>> +
>>>> +void drm_client_dev_register(struct drm_device *dev);
>>>> +void drm_client_dev_unregister(struct drm_device *dev);
>>>> +void drm_client_dev_hotplug(struct drm_device *dev);
>>>> +void drm_client_dev_lastclose(struct drm_device *dev);
>>>> +
>>>> +int drm_client_get_file(struct drm_client_dev *client);
>>>> +void drm_client_put_file(struct drm_client_dev *client);
>>>> +struct drm_event *
>>>> +drm_client_read_event(struct drm_client_dev *client, bool block);
>>>> +
>>>> +struct drm_client_connector {
>>>> +	unsigned int conn_id;
>>>> +	unsigned int status;
>>>> +	unsigned int crtc_id;
>>>> +	struct drm_mode_modeinfo *modes;
>>>> +	unsigned int num_modes;
>>>> +	bool has_tile;
>>>> +	int tile_group;
>>>> +	u8 tile_h_loc, tile_v_loc;
>>>> +};
>>>> +
>>>> +struct drm_client_display {
>>>> +	struct drm_client_dev *client;
>>>> +
>>>> +	struct drm_client_connector **connectors;
>>>> +	unsigned int num_connectors;
>>>> +
>>>> +	struct mutex modes_lock;
>>>> +	struct drm_mode_modeinfo *modes;
>>>> +	unsigned int num_modes;
>>>> +
>>>> +	bool cloned;
>>>> +	bool no_flushing;
>>>> +};
>>>> +
>>>> +void drm_client_display_free(struct drm_client_display *display);
>>>> +struct drm_client_display *
>>>> +drm_client_display_get_first_enabled(struct drm_client_dev *client, bool strict);
>>>> +
>>>> +int drm_client_display_update_modes(struct drm_client_display *display,
>>>> +				    bool *mode_changed);
>>>> +
>>>> +static inline bool
>>>> +drm_client_display_is_tiled(struct drm_client_display *display)
>>>> +{
>>>> +	return !display->cloned && display->num_connectors > 1;
>>>> +}
>>>> +
>>>> +int drm_client_display_dpms(struct drm_client_display *display, int mode);
>>>> +int drm_client_display_wait_vblank(struct drm_client_display *display);
>>>> +
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_first_mode(struct drm_client_display *display);
>>>> +struct drm_mode_modeinfo *
>>>> +drm_client_display_next_mode(struct drm_client_display *display,
>>>> +			     struct drm_mode_modeinfo *mode);
>>>> +
>>>> +#define drm_client_display_for_each_mode(display, mode) \
>>>> +	for (mode = drm_client_display_first_mode(display); mode; \
>>>> +	     mode = drm_client_display_next_mode(display, mode))
>>>> +
>>>> +unsigned int
>>>> +drm_client_display_preferred_depth(struct drm_client_display *display);
>>>> +
>>>> +int drm_client_display_commit_mode(struct drm_client_display *display,
>>>> +				   u32 fb_id, struct drm_mode_modeinfo *mode);
>>>> +unsigned int drm_client_display_current_fb(struct drm_client_display *display);
>>>> +int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
>>>> +			     struct drm_clip_rect *clips, unsigned int num_clips);
>>>> +int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
>>>> +				 bool event);
>>>> +
>>>> +struct drm_client_buffer {
>>>> +	struct drm_client_dev *client;
>>>> +	u32 width;
>>>> +	u32 height;
>>>> +	u32 format;
>>>> +	u32 handle;
>>>> +	u32 pitch;
>>>> +	u64 size;
>>>> +	struct dma_buf *dma_buf;
>>>> +	void *vaddr;
>>>> +
>>>> +	unsigned int *fb_ids;
>>>> +	unsigned int num_fbs;
>>>> +};
>>>> +
>>>> +struct drm_client_buffer *
>>>> +drm_client_framebuffer_create(struct drm_client_dev *client,
>>>> +			      struct drm_mode_modeinfo *mode, u32 format);
>>>> +void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
>>>> +struct drm_client_buffer *
>>>> +drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
>>>> +			 u32 format);
>>>> +void drm_client_buffer_delete(struct drm_client_buffer *buffer);
>>>> +int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
>>>> +			    struct drm_mode_modeinfo *mode);
>>>> +int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
>>>> diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
>>>> index 7c4fa32f3fc6..32dfed3d5a86 100644
>>>> --- a/include/drm/drm_device.h
>>>> +++ b/include/drm/drm_device.h
>>>> @@ -67,6 +67,7 @@ struct drm_device {
>>>>    	struct mutex filelist_mutex;
>>>>    	struct list_head filelist;
>>>> +	struct list_head filelist_internal;
>>>>    	/** \name Memory management */
>>>>    	/*@{ */
>>>> diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
>>>> index 5176c3797680..39af8a4be7b3 100644
>>>> --- a/include/drm/drm_file.h
>>>> +++ b/include/drm/drm_file.h
>>>> @@ -248,6 +248,13 @@ struct drm_file {
>>>>    	 */
>>>>    	void *driver_priv;
>>>> +	/**
>>>> +	 * @user_priv:
>>>> +	 *
>>>> +	 * Optional pointer for user private data. Useful for in-kernel clients.
>>>> +	 */
>>>> +	void *user_priv;
>>>> +
>>>>    	/**
>>>>    	 * @fbs:
>>>>    	 *
>>>> -- 
>>>> 2.15.1
>>>>



More information about the Intel-gfx mailing list