An example to showcase the client API. TODO: - A bootsplash client needs a way to tell drm_fb_helper to stay away, otherwise it will chime in on setup and hotplug. Most DRM drivers register fbdev before calling drm_dev_register() (the generic emulation is an exception). This have to be reversed for bootsplash to fend off fbdev. - Probably need some way to determine which is the primary display/device on multi DRM device systems. - Maybe do handover from early/simple DRM driver Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- drivers/gpu/drm/Kconfig | 5 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_bootsplash.c | 358 +++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_client.c | 7 + drivers/gpu/drm/drm_drv.c | 4 + include/drm/drm_client.h | 3 + 6 files changed, 378 insertions(+) create mode 100644 drivers/gpu/drm/drm_bootsplash.c diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index a461f078a0dd..aede0de0c9d9 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -65,6 +65,11 @@ config DRM_DEBUG_SELFTEST If in doubt, say "N". +config DRM_CLIENT_BOOTSPLASH + bool "DRM Bootsplash" + help + DRM Bootsplash + config DRM_KMS_HELPER tristate depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 9ead6436fb87..78573ed02a4e 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -31,6 +31,7 @@ drm-$(CONFIG_OF) += drm_of.o drm-$(CONFIG_AGP) += drm_agpsupport.o drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o +drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += drm_bootsplash.o drm_vram_helper-y := drm_gem_vram_helper.o \ drm_vram_helper_common.o \ diff --git a/drivers/gpu/drm/drm_bootsplash.c b/drivers/gpu/drm/drm_bootsplash.c new file mode 100644 index 000000000000..f58ee19e268f --- /dev/null +++ b/drivers/gpu/drm/drm_bootsplash.c @@ -0,0 +1,358 @@ +/* DRM internal client example */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/keyboard.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <drm/drm_client.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_modes.h> +#include <drm/drm_print.h> + +// drm_lastclose() +#include "drm_internal.h" + +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]"); + +struct drm_bootsplash { + struct drm_client_dev client; + struct mutex lock; + struct work_struct worker; + struct drm_client_buffer *buffers[2]; + bool started; + bool stop; +}; + +static bool drm_bootsplash_key_pressed; + +static int drm_bootsplash_keyboard_notifier_call(struct notifier_block *blk, + unsigned long code, void *_param) +{ + /* Any key is good */ + drm_bootsplash_key_pressed = true; + + return NOTIFY_OK; +} + +static struct notifier_block drm_bootsplash_keyboard_notifier_block = { + .notifier_call = drm_bootsplash_keyboard_notifier_call, +}; + +static void drm_bootsplash_buffer_delete(struct drm_bootsplash *splash) +{ + unsigned int i; + + for (i = 0; i < 2; i++) { + if (!IS_ERR_OR_NULL(splash->buffers[i])) + drm_client_framebuffer_delete(splash->buffers[i]); + splash->buffers[i] = NULL; + } +} + +static int drm_bootsplash_buffer_create(struct drm_bootsplash *splash, u32 width, u32 height) +{ + unsigned int i; + + for (i = 0; i < 2; i++) { + splash->buffers[i] = drm_client_framebuffer_create(&splash->client, width, height, DRM_FORMAT_XRGB8888); + if (IS_ERR(splash->buffers[i])) { + drm_bootsplash_buffer_delete(splash); + return PTR_ERR(splash->buffers[i]); + } + } + + return 0; +} + +static int drm_bootsplash_display_probe(struct drm_bootsplash *splash) +{ + struct drm_client_dev *client = &splash->client; + unsigned int width = 0, height = 0; + unsigned int num_non_tiled = 0, i; + unsigned int modeset_mask = 0; + struct drm_mode_set *modeset; + bool tiled = false; + int ret; + + ret = drm_client_modeset_probe(client, 0, 0); + if (ret) + return ret; + + mutex_lock(&client->modeset_mutex); + + drm_client_for_each_modeset(modeset, client) { + if (!modeset->mode) + continue; + + if (modeset->connectors[0]->has_tile) + tiled = true; + else + num_non_tiled++; + } + + if (!tiled && !num_non_tiled) { + drm_bootsplash_buffer_delete(splash); + ret = -ENOENT; + goto out; + } + + /* Assume only one tiled monitor is possible */ + if (tiled) { + int hdisplay = 0, vdisplay = 0; + + i = 0; + drm_client_for_each_modeset(modeset, client) { + i++; + if (!modeset->connectors[0]->has_tile) + continue; + + if (!modeset->y) + hdisplay += modeset->mode->hdisplay; + if (!modeset->x) + vdisplay += modeset->mode->vdisplay; + modeset_mask |= BIT(i - 1); + } + + width = hdisplay; + height = vdisplay; + + goto trim; + } + + /* The rest have one display (maybe cloned) per modeset, pick the largest */ + i = 0; + drm_client_for_each_modeset(modeset, client) { + i++; + if (!modeset->mode || modeset->connectors[0]->has_tile) + continue; + + if (modeset->mode->hdisplay * modeset->mode->vdisplay > width * height) { + width = modeset->mode->hdisplay; + height = modeset->mode->vdisplay; + modeset_mask = BIT(i - 1); + } + } + +trim: + /* Remove unused modesets (warning in __drm_atomic_helper_set_config()) */ + i = 0; + drm_client_for_each_modeset(modeset, client) { + unsigned int j; + + if (modeset_mask & BIT(i++)) + continue; + drm_mode_destroy(client->dev, modeset->mode); + modeset->mode = NULL; + + for (j = 0; j < modeset->num_connectors; j++) { + drm_connector_put(modeset->connectors[j]); + modeset->connectors[j] = NULL; + } + modeset->num_connectors = 0; + } + + if (!splash->buffers[0] || + splash->buffers[0]->fb->width != width || + splash->buffers[0]->fb->height != height) { + drm_bootsplash_buffer_delete(splash); + ret = drm_bootsplash_buffer_create(splash, width, height); + } + +out: + mutex_unlock(&client->modeset_mutex); + + return ret; +} + +static int drm_bootsplash_display_commit_buffer(struct drm_bootsplash *splash, unsigned int num) +{ + struct drm_client_dev *client = &splash->client; + struct drm_mode_set *modeset; + + mutex_lock(&client->modeset_mutex); + drm_client_for_each_modeset(modeset, client) { + if (modeset->mode) + modeset->fb = splash->buffers[num]->fb; + } + mutex_unlock(&client->modeset_mutex); + + return drm_client_modeset_commit(client); +} + +static u32 drm_bootsplash_color_table[3] = { + 0x00ff0000, 0x0000ff00, 0x000000ff, +}; + +/* Draw a box with changing colors */ +static void drm_bootsplash_draw_box(struct drm_client_buffer *buffer, unsigned int sequence) +{ + unsigned int width = buffer->fb->width; + unsigned int height = buffer->fb->height; + unsigned int x, y; + u32 *pix; + + pix = buffer->vaddr; + pix += ((height / 2) - 50) * width; + pix += (width / 2) - 50; + + for (y = 0; y < 100; y++) { + for (x = 0; x < 100; x++) + *pix++ = drm_bootsplash_color_table[sequence]; + pix += width - 100; + } +} + +static int drm_bootsplash_draw(struct drm_bootsplash *splash, unsigned int sequence, unsigned int buffer_num) +{ + if (!splash->buffers[buffer_num]) + return -ENOENT; + + DRM_DEBUG_KMS("draw: buffer_num=%u, sequence=%u\n", buffer_num, sequence); + + drm_bootsplash_draw_box(splash->buffers[buffer_num], sequence); + + return drm_bootsplash_display_commit_buffer(splash, buffer_num); +} + +static void drm_bootsplash_worker(struct work_struct *work) +{ + struct drm_bootsplash *splash = container_of(work, struct drm_bootsplash, worker); + struct drm_client_dev *client = &splash->client; + struct drm_device *dev = client->dev; + unsigned int buffer_num = 0, sequence = 0; + bool stop = false; + int ret = 0; + + while (!drm_bootsplash_key_pressed) { + mutex_lock(&splash->lock); + + stop = splash->stop; + + buffer_num = !buffer_num; + + ret = drm_bootsplash_draw(splash, sequence, buffer_num); + + mutex_unlock(&splash->lock); + + if (stop || ret == -ENOENT || ret == -EBUSY) + break; + + if (++sequence == 3) + sequence = 0; + + msleep(500); + } + + /* Restore fbdev (or other) on key press. */ + /* TODO: Check if it's OK to call drm_lastclose here. */ + if (drm_bootsplash_key_pressed && !splash->stop) + drm_lastclose(dev); + + drm_bootsplash_buffer_delete(splash); + + DRM_DEV_DEBUG_KMS(dev->dev, "Bootsplash has stopped (key=%u stop=%u, ret=%d).\n", + drm_bootsplash_key_pressed, splash->stop, ret); +} + +static int drm_bootsplash_client_hotplug(struct drm_client_dev *client) +{ + struct drm_bootsplash *splash = container_of(client, struct drm_bootsplash, client); + int ret = 0; + + if (drm_bootsplash_key_pressed) + return 0; + + mutex_lock(&splash->lock); + + if (splash->stop) + goto out_unlock; + + ret = drm_bootsplash_display_probe(splash); + if (ret < 0) { + if (splash->started && ret == -ENOENT) + splash->stop = true; + goto out_unlock; + } + + /* + * TODO: Deal with rotated panels, might have to sw rotate + if (drm_client_panel_rotation(...)) + */ + + if (!splash->started) { + splash->started = true; + schedule_work(&splash->worker); + } + +out_unlock: + mutex_unlock(&splash->lock); + + return ret; +} + +static void drm_bootsplash_client_unregister(struct drm_client_dev *client) +{ + struct drm_bootsplash *splash = container_of(client, struct drm_bootsplash, client); + + DRM_DEBUG_KMS("%s: IN\n", __func__); + + mutex_lock(&splash->lock); + splash->stop = true; + mutex_unlock(&splash->lock); + + flush_work(&splash->worker); + + drm_client_release(client); + kfree(splash); + + unregister_keyboard_notifier(&drm_bootsplash_keyboard_notifier_block); + + DRM_DEBUG_KMS("%s: OUT\n", __func__); +} + +static const struct drm_client_funcs drm_bootsplash_client_funcs = { + .owner = THIS_MODULE, + .unregister = drm_bootsplash_client_unregister, + .hotplug = drm_bootsplash_client_hotplug, +}; + +void drm_bootsplash_client_register(struct drm_device *dev) +{ + struct drm_bootsplash *splash; + int ret; + + if (!drm_bootsplash_enabled) + return; + + splash = kzalloc(sizeof(*splash), GFP_KERNEL); + if (!splash) + return; + + ret = drm_client_init(dev, &splash->client, "bootsplash", &drm_bootsplash_client_funcs); + if (ret) { + DRM_DEV_ERROR(dev->dev, "Failed to create client, ret=%d\n", ret); + kfree(splash); + return; + } + + /* For this simple example only allow the first */ + drm_bootsplash_enabled = false; + + mutex_init(&splash->lock); + + INIT_WORK(&splash->worker, drm_bootsplash_worker); + + register_keyboard_notifier(&drm_bootsplash_keyboard_notifier_block); + + drm_bootsplash_client_hotplug(&splash->client); + + drm_client_register(&splash->client); +} diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index 77aa1cf09caa..bf7badcde2f0 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -161,6 +161,13 @@ void drm_client_release(struct drm_client_dev *client) } EXPORT_SYMBOL(drm_client_release); +void drm_client_dev_register(struct drm_device *dev) +{ +#if CONFIG_DRM_CLIENT_BOOTSPLASH + drm_bootsplash_client_register(dev); +#endif +} + void drm_client_dev_unregister(struct drm_device *dev) { struct drm_client_dev *client, *tmp; diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 862621494a93..317b8cf67980 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -1014,6 +1014,10 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags) drm_minor_unregister(dev, DRM_MINOR_RENDER); out_unlock: mutex_unlock(&drm_global_mutex); + + if (!ret) + drm_client_dev_register(dev); + return ret; } EXPORT_SYMBOL(drm_dev_register); diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index f2d5ed745733..e9eb770093fb 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -107,6 +107,7 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client, void drm_client_release(struct drm_client_dev *client); void drm_client_register(struct drm_client_dev *client); +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_restore(struct drm_device *dev); @@ -183,4 +184,6 @@ int drm_client_modeset_dpms(struct drm_client_dev *client, int mode); int drm_client_debugfs_init(struct drm_minor *minor); +void drm_bootsplash_client_register(struct drm_device *dev); + #endif -- 2.20.1 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel