Skip to content

Commit

Permalink
vt(4) integration: New vt_drmfb backend to integrate with `drm_fb_h…
Browse files Browse the repository at this point in the history
…elper`

[Why]
The goal is to make the framebuffer behavior closer to what happens on
Linux.

One major difference is that on FreeBSD, we used to pass the screen base
address to `vt_fb` during initialization, then `vt_fb` would write
directly to that memory region. This worked fine for years.
Unfortunately, this broke somehow with the backport of DRM from Linux
5.17. At that point, the screen was not refreshed anymore, even though
`vt_fb` wrote pixels to the correct memory region.

The only time the screen was refreshed, it was during a vt-switch. The
screen then displayed the content of the tty we just switched away, but
still, something happened on the screen. And that's the only operation
we called into `drm_fb_helper` from `vt_fb`.

On Linux, `fbcon` calls into `fbdev`, emulated by `drm_fb_helper` for
each access (read & write) to the framebuffer. It never reads or writes
to the memory region on its own.

[How]
Therefore we need a new integration layer to make sure vt(4) calls into
`drm_fb_helper` like Linux' fbcon does. We do this by adding a new vt(4)
backend called `vt_drmfb`, which replaces `vt_fb`.

`vt_drmfb` bridges between:
* DRM's `drm_fb_helper` emulation of Linux' fbdev below, and
* vt(4) console handling above

In other words, it maps fbdev callbacks to vt(4)'s backend expected
callbacks.

`vt_drmfb` takes care of converting the vt(4) callbacks arguments to
whatever is expected by the underlying fbdev emulation. Then, when it's
time to write to the actual framebuffer memory, the implementation of
`cfb_*` functions are derived from the `vt_fb` "setpixel" code.

`vt_drmfb` is maintained inside drm-kmod and is compiled inside drm(4).
It is not under `sys/dev/vt/hw`. The reason is that `vt_drmfb` needs to
know the `drm_fb_helper` API and must evolve with it.

`vt_drmfb` has another difference: it doesn't use a taskqueue(9) to call
into `drm_fb_helper as `vt_fb` did. It performs the calls synchronously.
`drm_fb_helper` already uses a Linux workqueue to make updates
asynchronous with the underlying driver. This simplifies the integration
a lot because the whole `fb_mode_task`-related machinery is removed from
`drm_os_freebsd.c` and `linux_fb.c`.

Because we don't need that taskqueue(9) anymore, this patch also gets
rid of the `struct vt_kms_softc` indirection. Now, the `struct
drm_fb_helper` pointed is stored directly into FreeBSD's `struct
fb_info->fb_priv`.
  • Loading branch information
dumbbell committed Apr 14, 2023
1 parent d271a20 commit bdf9d69
Show file tree
Hide file tree
Showing 7 changed files with 582 additions and 86 deletions.
4 changes: 1 addition & 3 deletions drivers/gpu/drm/drm_fb_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -1887,9 +1887,7 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper,

#ifdef __FreeBSD__
info->fb_bsddev = fb_helper->dev->dev->bsddev;
struct vt_kms_softc *sc = (struct vt_kms_softc *)info->fbio.fb_priv;
if (sc)
sc->fb_helper = fb_helper;
info->fbio.fb_priv = fb_helper;
#endif

/* Need to drop locks to avoid recursive deadlock in
Expand Down
58 changes: 1 addition & 57 deletions drivers/gpu/drm/drm_os_freebsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ SYSCTL_NODE(_dev, OID_AUTO, drm, CTLFLAG_RW, 0, "DRM args (compat)");
SYSCTL_INT(_dev_drm, OID_AUTO, __drm_debug, CTLFLAG_RWTUN, &__drm_debug, 0, "drm debug flags (compat)");
SYSCTL_NODE(_hw, OID_AUTO, dri, CTLFLAG_RW, 0, "DRI args");
SYSCTL_INT(_hw_dri, OID_AUTO, __drm_debug, CTLFLAG_RWTUN, &__drm_debug, 0, "drm debug flags");
static int skip_ddb;
int skip_ddb;
SYSCTL_INT(_dev_drm, OID_AUTO, skip_ddb, CTLFLAG_RWTUN, &skip_ddb, 0, "go straight to dumping core (compat)");
SYSCTL_INT(_hw_dri, OID_AUTO, skip_ddb, CTLFLAG_RWTUN, &skip_ddb, 0, "go straight to dumping core");
#if defined(DRM_DEBUG_LOG_ALL)
Expand All @@ -44,8 +44,6 @@ int drm_debug_persist = 0;
SYSCTL_INT(_dev_drm, OID_AUTO, drm_debug_persist, CTLFLAG_RWTUN, &drm_debug_persist, 0, "keep drm debug flags post-load (compat)");
SYSCTL_INT(_hw_dri, OID_AUTO, drm_debug_persist, CTLFLAG_RWTUN, &drm_debug_persist, 0, "keep drm debug flags post-load");

static bool already_switching_inside_panic = false;

static struct callout reset_debug_log_handle;

static void
Expand Down Expand Up @@ -83,60 +81,6 @@ sysctl_pci_id(SYSCTL_HANDLER_ARGS)

/* Framebuffer related code */

/* Call restore out of vt(9) locks. */
void
vt_restore_fbdev_mode(void *arg, int pending)
{
struct drm_fb_helper *fb_helper;
struct vt_kms_softc *sc;
struct mm_struct mm;

sc = (struct vt_kms_softc *)arg;
fb_helper = sc->fb_helper;
linux_set_current(curthread);
if(!fb_helper) {
DRM_DEBUG("fb helper is null!\n");
return;
}
drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
}

int
vt_kms_postswitch(void *arg)
{
struct vt_kms_softc *sc;

sc = (struct vt_kms_softc *)arg;

if (!kdb_active && !KERNEL_PANICKED()) {
taskqueue_enqueue(taskqueue_thread, &sc->fb_mode_task);

/* XXX the VT_ACTIVATE IOCTL must be synchronous */
if (curthread->td_proc->p_pid != 0 &&
taskqueue_member(taskqueue_thread, curthread) == 0)
taskqueue_drain(taskqueue_thread, &sc->fb_mode_task);
} else {
#ifdef DDB
db_trace_self_depth(10);
mdelay(1000);
#endif
if (already_switching_inside_panic || skip_ddb) {
spinlock_enter();
doadump(false);
EVENTHANDLER_INVOKE(shutdown_final, RB_NOSYNC);
}
linux_set_current(curthread);
if(!sc->fb_helper) {
DRM_DEBUG("fb helper is null!\n");
return -1;
}
already_switching_inside_panic = true;
drm_fb_helper_restore_fbdev_mode_unlocked(sc->fb_helper);
already_switching_inside_panic = false;
}
return (0);
}

int
drm_dev_alias(struct device *ldev, struct drm_minor *minor, const char *minor_str)
{
Expand Down
9 changes: 1 addition & 8 deletions drivers/gpu/drm/drm_os_freebsd.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ __FBSDID("$FreeBSD$");
#define DRM_DEV_UID UID_ROOT
#define DRM_DEV_GID GID_VIDEO

struct vt_kms_softc {
struct drm_fb_helper *fb_helper;
struct task fb_mode_task;
};

/* XXXKIB what is the right code for the FreeBSD ? */
/* kib@ used ENXIO here -- dumbbell@ */
#define EREMOTEIO EIO
Expand All @@ -37,14 +32,12 @@ MALLOC_DECLARE(DRM_MEM_DRIVER);
MALLOC_DECLARE(DRM_MEM_KMS);

extern devclass_t drm_devclass;
extern int skip_ddb;

struct drm_minor;
int drm_dev_alias(struct device *dev, struct drm_minor *minor, const char *minor_str);
void cancel_reset_debug_log(void);

void vt_restore_fbdev_mode(void *arg, int pending);
int vt_kms_postswitch(void *arg);

#if 0
struct linux_fb_info;
static inline void vga_switcheroo_unregister_client(struct pci_dev *pdev) {}
Expand Down
148 changes: 131 additions & 17 deletions drivers/gpu/drm/linux_fb.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ __FBSDID("$FreeBSD$");
#include <vm/vm_phys.h>

#include <dev/vt/vt.h>
#include <dev/vt/hw/fb/vt_fb.h>
#include "vt_drmfb.h"

#include <drm/drm_fb_helper.h>
#include <linux/fb.h>
Expand Down Expand Up @@ -118,14 +118,8 @@ struct linux_fb_info *
framebuffer_alloc(size_t size, struct device *dev)
{
struct linux_fb_info *info;
struct vt_kms_softc *sc;

info = malloc(sizeof(*info) + size, LKPI_FB_MEM, M_WAITOK | M_ZERO);
sc = malloc(sizeof(*sc), LKPI_FB_MEM, M_WAITOK | M_ZERO);
TASK_INIT(&sc->fb_mode_task, 0, vt_restore_fbdev_mode, sc);

info->fbio.fb_priv = sc;
info->fbio.enter = &vt_kms_postswitch;

if (size)
info->par = info + 1;
Expand All @@ -138,14 +132,9 @@ framebuffer_alloc(size_t size, struct device *dev)
void
framebuffer_release(struct linux_fb_info *info)
{
struct vt_kms_softc *sc;

if (info == NULL)
return;
if (info->fbio.fb_priv)
sc = info->fbio.fb_priv;
kfree(info->apertures);
free(info->fbio.fb_priv, LKPI_FB_MEM);
free(info, LKPI_FB_MEM);
}

Expand Down Expand Up @@ -211,8 +200,7 @@ __register_framebuffer(struct linux_fb_info *fb_info)
VM_MEMATTR_UNCACHEABLE);
#endif

fb_helper =
((struct vt_kms_softc *)fb_info->fbio.fb_priv)->fb_helper;
fb_helper = (struct drm_fb_helper *)fb_info->fbio.fb_priv;
fb_info->fbio.fb_video_dev =
device_get_parent(fb_helper->dev->dev->bsddev);
fb_info->fbio.fb_name =
Expand All @@ -232,14 +220,14 @@ __register_framebuffer(struct linux_fb_info *fb_info)
fb_info->fbio.fb_fbd_dev = device_add_child(fb_info->fb_bsddev, "fbd",
device_get_unit(fb_info->fb_bsddev));

/* tell vt_fb to initialize color map */
/* tell vt_drmfb to initialize color map */
fb_info->fbio.fb_cmsize = 0;
if (fb_info->fbio.fb_bpp == 0) {
device_printf(fb_info->fbio.fb_fbd_dev,
"fb_bpp not set, setting to 8\n");
fb_info->fbio.fb_bpp = 32;
}
if ((err = vt_fb_attach(&fb_info->fbio)) != 0) {
if ((err = vt_drmfb_attach(&fb_info->fbio)) != 0) {
switch (err) {
case EEXIST:
device_printf(fb_info->fbio.fb_fbd_dev,
Expand Down Expand Up @@ -283,7 +271,7 @@ __unregister_framebuffer(struct linux_fb_info *fb_info)
mtx_unlock(&Giant);
fb_info->fbio.fb_fbd_dev = NULL;
}
vt_fb_detach(&fb_info->fbio);
vt_drmfb_detach(&fb_info->fbio);

if (fb_info->fbops->fb_destroy)
fb_info->fbops->fb_destroy(fb_info);
Expand Down Expand Up @@ -333,46 +321,172 @@ linux_fb_get_options(const char *connector_name, char **option)
return (*option != NULL ? 0 : -ENOENT);
}

/*
* Routines to write to the framebuffer. They are used to implement Linux'
* fbdev equivalent functions below.
*
* Copied from `sys/dev/vt/hw/fb/vt_fb.c`.
*/

static void
fb_mem_wr1(struct linux_fb_info *info, uint32_t offset, uint8_t value)
{
KASSERT(
(offset < info->screen_size),
("Offset %#08x out of framebuffer size", offset));
*(uint8_t *)(info->screen_base + offset) = value;
}

static void
fb_mem_wr2(struct linux_fb_info *info, uint32_t offset, uint16_t value)
{
KASSERT(
(offset < info->screen_size),
("Offset %#08x out of framebuffer size", offset));
*(uint16_t *)(info->screen_base + offset) = value;
}

static void
fb_mem_wr4(struct linux_fb_info *info, uint32_t offset, uint32_t value)
{
KASSERT(
(offset < info->screen_size),
("Offset %#08x out of framebuffer size", offset));
*(uint32_t *)(info->screen_base + offset) = value;
}

static void
fb_setpixel(struct linux_fb_info *info, uint32_t x, uint32_t y,
uint32_t color)
{
uint32_t bytes_per_pixel;
unsigned int offset;

bytes_per_pixel = info->var.bits_per_pixel / 8;
offset = info->fix.line_length * y + x * bytes_per_pixel;

KASSERT((info->screen_base != 0), ("Unmapped framebuffer"));

switch (bytes_per_pixel) {
case 1:
fb_mem_wr1(info, offset, color);
break;
case 2:
fb_mem_wr2(info, offset, color);
break;
case 3:
fb_mem_wr1(info, offset, (color >> 16) & 0xff);
fb_mem_wr1(info, offset + 1, (color >> 8) & 0xff);
fb_mem_wr1(info, offset + 2, color & 0xff);
break;
case 4:
fb_mem_wr4(info, offset, color);
break;
default:
/* panic? */
return;
}
}

void
cfb_fillrect(struct linux_fb_info *info, const struct fb_fillrect *rect)
{
uint32_t x, y;

if (info->fbio.fb_flags & FB_FLAG_NOWRITE)
return;

KASSERT(
(rect->rop == ROP_COPY),
("`rect->rop=%u` is unsupported in cfb_fillrect()", rect->rop));

for (y = rect->dy; y < rect->height; ++y) {
for (x = rect->dx; x < rect->width; ++x) {
fb_setpixel(info, x, y, rect->color);
}
}
}

void
cfb_copyarea(struct linux_fb_info *info, const struct fb_copyarea *area)
{
panic("Not implemented");
}

void
cfb_imageblit(struct linux_fb_info *info, const struct fb_image *image)
{
uint32_t x, y, width, height, xi, yi;
uint32_t bytes_per_img_line, bit, byte, color;

if (info->fbio.fb_flags & FB_FLAG_NOWRITE)
return;

KASSERT(
(image->depth == 1),
("`image->depth=%u` is unsupported in cfb_imageblit()",
image->depth));

bytes_per_img_line = (image->width + 7) / 8;

x = image->dx;
y = image->dy;
width = image->width;
height = image->height;

if (x + width > info->var.xres) {
if (x >= info->var.xres)
return;
width = info->var.xres - x;
}
if (y + height > info->var.yres) {
if (y >= info->var.yres)
return;
height = info->var.yres - y;
}

for (yi = 0; yi < height; ++yi) {
for (xi = 0; xi < width; ++xi) {
byte = yi * bytes_per_img_line + xi / 8;
bit = 0x80 >> (xi % 8);
color = image->data[byte] & bit ?
image->fg_color : image->bg_color;

fb_setpixel(info, x + xi, y + yi, color);
}
}
}

void
sys_fillrect(struct linux_fb_info *info, const struct fb_fillrect *rect)
{
cfb_fillrect(info, rect);
}

void
sys_copyarea(struct linux_fb_info *info, const struct fb_copyarea *area)
{
cfb_copyarea(info, area);
}

void
sys_imageblit(struct linux_fb_info *info, const struct fb_image *image)
{
cfb_imageblit(info, image);
}

ssize_t
fb_sys_read(struct linux_fb_info *info, char __user *buf,
size_t count, loff_t *ppos)
{
panic("Not implemented");
return (0);
}

ssize_t
fb_sys_write(struct linux_fb_info *info, const char __user *buf,
size_t count, loff_t *ppos)
{
panic("Not implemented");
return (0);
}
Loading

0 comments on commit bdf9d69

Please sign in to comment.