Search by Tags

Framebuffer (Linux)

 

Article updated at 13 Apr 2022
Compare with Revision




Attention: this article used to describe a lot of information about display timings and resolution. The content has been moved to a generic article: Display Outputs, Resolution and Timings.

Warning: this article is not maintained anymore and is kept for reference only.

Introduction

The framebuffer (fbdev) is a character device providing access to graphics hardware. Beside the framebuffer, other interfaces exist to access graphics hardware such as the DRI (direct rendering interface) or proprietary interfaces (NVIDIA drivers). Typically, the framebuffer doesn't support any 2D/3D hardware acceleration.

The i.MX 6 kernel comes with an open source fbdev driver called mxcfb. The X-Server driver is closed source and called vivante.

The i.MX 7 and i.MX 6ULL kernels come with an open source fbdev driver, mxsfb.

Our Tegra kernel comes with an open source fbdev driver called tegrafb. In contrast, the X-Server driver, just called tegra, is available as binary driver only.

The Vybrid kernel 4.1 and older come with a fbdev driver called dcu. Kernels 4.4 and newer come with a DRM driver which supports the fbdev interface via fbdev emulation.

Framebuffer Console

All modules use by default the kernel internal fbdev based console which displays the kernel and system log messages as well as the virtual terminals. Since the fbdev console is initialized by the kernel early during boot up, it can be used to show a splash screen (rather then a console). Refer to the Splash Screen (Linux) article for detailed instructions.

Disable Framebuffer Console

To disable console messages on the screen remove the "console=tty1" kernel command line parameter from the setup environment variable in U-Boot. The content of setup is module-specific, use print to show the initial content:

Colibri VFxx # print
...
setup=setenv setupargs console=tty1 console=${console},${baudrate}n8 ${memargs} consoleblank=0
...
Colibri VFxx # setenv setup 'setenv setupargs console=${console},${baudrate}n8 ${memargs} consoleblank=0'
Colibri VFxx # savee

Note: If you also remove the console= entry for the serial console Linux will fallback to the framebuffer console again. Use an invalid console (e.g. console=null) or remove framebuffer console support from the kernel (see below) to work around this.

Here are some kernel arguments that may help disable the console, you must try a combination of them on your SoM:

  • vt.global_cursor_default=0
  • consoleblank=0
  • console=null or console=${console}
  • earlycon

systemd still initialize a getty on tty1. This can be easily disabled using:

# systemctl disable getty@tty1.service

Remove Framebuffer Console

To get rid of console messages and any system output, the framebuffer console support can be removed from the kernel entirely by removing the kernel configuration "CONFIG_FRAMEBUFFER_CONSOLE".

Redirect Framebuffer Console

The environment variable fbcon can be used to specify the fbdev device used for the console. Use the following command to use fb1 as console:

Colibri T20 # setenv setup "${setup} fbcon=map:1"

To switch the console after boot one can use the con2fbmap tool which can easily be built using OpenEmbedded. Switching as follows:

# con2fbmap 1 1

More information about the fbdev console can be found in the kernel documentation:

http://git.toradex.com/cgit/linux-toradex.git/tree/Documentation/fb/fbcon.txt?h=tegra

Tegra Console Output

BSP v2.0 and higher provide by default a console via DVI-A/LVDS/RGB/VGA (LVDS-1 in X-Server terms) and optionally via DVI-D (HDMI-1 in X-Server terms). To activate the framebuffer console during boot on DVI-D instead of VGA one just has to set fbcon=map:1 in U-Boot as follows:

Colibri T30 # setenv setup 'setenv setupargs asix_mac=${ethaddr} no_console_suspend=1 console=tty1 console=ttyS0,${baudrate}n8 debug_uartport=lsport,0 ${memargs} fbcon=map:1'

On Apalis T30 we are using the HDMI interface for the framebuffer console by default as unfortunately VGA is not supported on NVIDIA's Tegra 3 SoC. To switch it back to parallel RGB (e.g. when using an LVDS or parallel RGB display) proceed as follows:

Apalis T30 # setenv vidargs 'video=tegrafb0:800x480-16@60'
Apalis T30 # setenv setup 'setenv setupargs gpt gpt_sector=${gptoffset} igb_mac=${ethaddr} no_console_suspend=1 console=tty1 console=ttyS0,${baudrate}n8 debug_uartport=lsport,0 ${memargs} fbcon=map:0'

Console Configuration

The utility setterm (available in the util-linux package) allows to configure the framebuffer console.

Note: Some commands are only available on the framebuffer console itself (and not from a SSH session).

For particular interest might be the power saving configurations such as powersave and blank. Using the blank parameter, one can define the time until the console turns black (in minutes, 0 means never). Using the powersave command, one can define the time until the framebuffer driver disables the output completely (after being in blank state, in minutes, 0 means immediately after going to blank mode).

setterm -blank 1
setterm -powerdown 0
setterm -powersave powerdown

There is also a Kernel boot parameter to configure console blanking called consoleblank (blanking time in seconds, 0 means never). To disable the blinking cursor, one can use the vt.global_cursor_default kernel parameter. Use the U-Boot console to configure the boot parameter:

# setenv defargs consoleblank=0 vt.global_cursor_default=0

Cairo Example

Cairo is a library which provides primitives for two dimensional drawing. This can be used to draw text and images on a framebuffer device.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <cairo/cairo.h>
#include <unistd.h>                                                                                                        
#include <sys/ioctl.h>
 
#define DELAY_IN_SECS    5
#define SCALE_WIDTH      256.0
#define SCALE_HEIGHT     256.0
 
typedef struct _cairo_linuxfb_device {
	int fb_fd;
	char *fb_data;
	long fb_screensize;
	struct fb_var_screeninfo fb_vinfo;
	struct fb_fix_screeninfo fb_finfo;
} cairo_linuxfb_device_t;
 
/* Destroy a cairo surface */
void cairo_linuxfb_surface_destroy(void *device)
{
	cairo_linuxfb_device_t *dev = (cairo_linuxfb_device_t *)device;
 
	if (dev == NULL)
		return;
 
	munmap(dev->fb_data, dev->fb_screensize);
	close(dev->fb_fd);
	free(dev);
}
 
/* Create a cairo surface using the specified framebuffer */
cairo_surface_t *cairo_linuxfb_surface_create(const char *fb_name)
{
	cairo_linuxfb_device_t *device;
	cairo_surface_t *surface;
 
	/* Use fb0 if no fram buffer is specified */
	if (fb_name == NULL) {
		fb_name = "/dev/fb0";
	}
 
	device = malloc(sizeof(*device));
	if (!device) {
		perror("Error: cannot allocate memory\n");
		exit(1);
	}
 
	// Open the file for reading and writing
	device->fb_fd = open(fb_name, O_RDWR);
	if (device->fb_fd == -1) {
		perror("Error: cannot open framebuffer device");
		goto handle_allocate_error;
	}
 
	// Get variable screen information
	if (ioctl(device->fb_fd, FBIOGET_VSCREENINFO, &device->fb_vinfo) == -1) {
		perror("Error: reading variable information");
		goto handle_ioctl_error;
	}
 
	// Figure out the size of the screen in bytes
	device->fb_screensize = device->fb_vinfo.xres * device->fb_vinfo.yres
	                        * device->fb_vinfo.bits_per_pixel / 8;
 
	// Map the device to memory
	device->fb_data = (char *)mmap(0, device->fb_screensize,
	                               PROT_READ | PROT_WRITE, MAP_SHARED,
	                               device->fb_fd, 0);
	if ((int)device->fb_data == -1) {
		perror("Error: failed to map framebuffer device to memory");
		goto handle_ioctl_error;
	}
 
	// Get fixed screen information
	if (ioctl(device->fb_fd, FBIOGET_FSCREENINFO, &device->fb_finfo) == -1) {
		perror("Error reading fixed information");
		goto handle_ioctl_error;
	}
 
	/* Create the cairo surface which will be used to draw to */
	surface = cairo_image_surface_create_for_data(device->fb_data,
	              CAIRO_FORMAT_RGB16_565,
	              device->fb_vinfo.xres,
	              device->fb_vinfo.yres,
	              cairo_format_stride_for_width(CAIRO_FORMAT_RGB16_565,
												device->fb_vinfo.xres));
	cairo_surface_set_user_data(surface, NULL, device,
								&cairo_linuxfb_surface_destroy);
 
	return surface;
 
handle_ioctl_error:
	close(device->fb_fd);
handle_allocate_error:
	free(device);
	exit(1);
}
 
int main(int argc, char *argv[]) {
	int image_width;
	int image_height;
	char frame_buffer_number;
	char fb_node[16] = {0};
	cairo_surface_t *surface;
	cairo_surface_t *image;
	cairo_t *cr;
 
	if (argc != 2) {
		printf("Usage: ./cairo /path/to/png/image\n");
		exit(1);
	}
 
	if (strstr(argv[1], ".png") == NULL) {
		printf("Only png images are supported with this example\n");
		exit(1);
	}		
 
	printf("Enter frame buffer number:\t");
	scanf("%c", &frame_buffer_number);
	sprintf(fb_node, "/dev/fb");
	fb_node[strlen(fb_node)] = frame_buffer_number;
	fb_node[strlen(fb_node)] = '\0';
	printf("Frame buffer node is: %s\n", fb_node);
 
	surface = cairo_linuxfb_surface_create(fb_node);
	cr = cairo_create(surface);
 
	/* 
	 * We clear the cairo surface here before drawing
	 * This is required in case something was drawn on this surface
	 * previously, the previous contents would not be cleared without this.
	 */
	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
	cairo_paint(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
 
	cairo_select_font_face(cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
	cairo_set_font_size(cr, 32.0);
	cairo_set_source_rgb(cr, 1.0, 1.0, 0.0);
	cairo_move_to(cr, 100, 300);
	cairo_show_text(cr, "Toradex Cairo Example!");
 
	/* Wait for the result of drawing operation to persist for the user to see */
	sleep(DELAY_IN_SECS);
	/* Clear the surface and prepare for a new drawing operation */
	cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
	cairo_paint(cr);
	cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
 
	image = cairo_image_surface_create_from_png(argv[1]);
	image_width = cairo_image_surface_get_width(image);
	image_height = cairo_image_surface_get_height(image);
 
	/* Scale the image arbitrarily */
	cairo_scale(cr, SCALE_WIDTH/image_width, SCALE_HEIGHT/image_height);
 
	cairo_set_source_surface(cr, image, 350, 200);
	cairo_paint(cr);
	/* Wait for the result of the drawing operation to persist for the user to see */
	sleep(DELAY_IN_SECS);
 
	/* Destroy and release all cairo related contexts */
	cairo_destroy(cr);
	cairo_surface_destroy(surface);
 
	return 0;
}

Below is the Makefile which can be used for building the above example code with make cairo assuming the SDK is setup as described in Linux SDKs article.

Note: All command lines must start with a tab instead of spaces in a Makefile

SYSROOTS = /usr/local/oecore-x86_64/sysroots
CC = ${SYSROOTS}/x86_64-angstromsdk-linux/usr/bin/arm-angstrom-linux-gnueabi/arm-angstrom-linux-gnueabi-gcc
LIB_PATH = "-L${SYSROOTS}/armv7at2hf-neon-angstrom-linux-gnueabi/lib/"
LIBS =-lcairo
CFLAGS = -O2 -g -mfloat-abi=hard --sysroot=${SYSROOTS}/armv7at2hf-neon-angstrom-linux-gnueabi/

all:
    ${CC} ${CFLAGS} ${LIB_PATH} ${LIBS} ${INCLUDES} -o cairo cairo.c

clean:
    rm -rf cairo

Please refer the Cairo documentation and samples.