How to set a video mode through libdrm
This is an attempt of a tutorial on using #kms to set a video mode. I got interested in this topic when I realized how many dependencies an #opengl interface from within #X generates. The classic alternative, to for example #freeglut or #glfw or plain X interfacing, is #svgalib , which bears the disadvantage of required superuser privileges. This is unacceptable from a game development point of view. With the advent of KMS (Kernel Mode Setting, since #linux 2.6.29) another possibility opened: #libdrm . Low level access to video hardware usually sounds like an undesirable amount of work. Here this was just the case due to the sparse documentation of the #drm interface; the coding is actually straight forward. Documenting my work here hopefully helps other developers to consider using kms as well as explaining some of the #drm functionality. Before I start with the code I want to thank +Jesse Barnes for his inspiring blog post ( http://virtuousgeek.org/blog/index.php/jbarnes/2011/10/31/writing_stanalone_programs_with_egl_and_ ) and his hints for sample sources. The following code is based upon them. Be advised that dabbeling in the depth of your video hardware might harm it and I will in no way be held accountable for consequential damages of any kind. I went to work pretty brutally on my #Intel #GMA though and I never had any problems.
So lets get started with the code. First we need to open the #dri device as follows:
fd = open("/dev/dri/card0",O_RDWR | O_CLOEXEC);
this will work from within X. Next, we acquire the drm resources, which will fail from within X.
resources = drmModeGetResources(fd);
To be able to restore everything after we are done, we obtain the current video mode and save the framebuffer identifier.
crtc = drmModeGetCrtc(fd,resources->crtcs[0]);
oid = crtc->buffer_id;
Now, we decide what connector we want to use. The connector is the physical connection from the graphics adapter to the display. To do this we cycle through all connectors and pick the working connector with the index given by conn.
for(i=0;i<resources->count_connectors;++i)
{
⠂connector = drmModeGetConnector(fd,resources->connectors[i]);
⠂if(connector==0 || conn--!=0) { continue; }
⠂if(connector->connection==DRM_MODE_CONNECTED && connector->count_modes>0) { break; }
⠂drmModeFreeConnector(connector);
}
Next, we need to choose the encoding our connector should use. This is the protocol of data encoding. We just want the first working encoder.
for(i=0;i<resources->count_encoders;++i)
{
⠂encoder = drmModeGetEncoder(fd,resources->encoders[i]);
⠂if(encoder==0) { continue; }
⠂if(encoder->encoder_id==connector->encoder_id) { break; }
⠂drmModeFreeEncoder(encoder);
}
Then we test for the desired video mode, more precisely its width and height in pixels (XRES,YRES), the number of bits-per-pixels is not important yet.
for(i=0;i<connector->count_modes;++i)
{
⠂mode = connector->modes[i];
⠂if( (mode.hdisplay==XRES) && (mode.vdisplay==YRES) ) { break; }
}
In case our requested mode is not found we can force drm to do by our bidding if we provide a mode ourselve. A mode is basically composed of a #modeline , the same used within X.
drmModeModeInfo mode = { clock,hdisplay,hsync_start,hsync_end,htotal,hskew,vdisplay,vsync_start,vsync_end,vtotal,vsync,vrefresh((1000*clock)/(htotal*vtotal)),flags,type,name };
Now we set up the #framebuffer . This is a slightly problematic part, since it seemed at first glance hardware specific, but I found the dumb framebuffer which should be abstract enough for our pruposes. We set off two ioctl's and map the resulting framebuffer pointer. Here we need the bits-per-pixels (BPP).
struct drm_mode_create_dumb dc = { YRES,XRES,BPP,0,0,0,0 };
drmIoctl(fd,DRM_IOCTL_MODE_CREATE_DUMB,&dc);
struct drm_mode_map_dumb dm = { dc.handle,0,0 };
drmIoctl(fd,DRM_IOCTL_MODE_MAP_DUMB,&dm);
front = (long*)mmap(0,dc.size,PROT_READ | PROT_WRITE, MAP_SHARED,fd,dm.offset);
Finally, we enlist this framebuffer and then set the video mode.
drmModeAddFB(fd,XRES,YRES,BPP,BPP,dc.pitch,dc.handle,&id);
drmModeSetCrtc(fd,encoder->crtc_id,id,0,0,&connector->connector_id,1,&mode);
The framebuffer (here named front) can be used now and its contents are displayed by flushing it as follows.
drmModeDirtyFB(fd,id,0,0);
That is it. Furthermore I posted a full code example (kms.cc) at:
http://pastebin.com/UFzr2gQ5
this #c++ code can be compiled with
g++ kms.cc -okms -ldrm -I/usr/include/libdrm
and should be used from a pure terminal, by switching with STRG+ALT+x to TTYx with for example x = 1. There are two commandline options for this program. First a boolean to set wether a mode shall be forced or not; second, an integer being the index of the connector that shall be used. How to restore the original state please see the code sample. To see how I use kms please see:
http://github.com/atcl/xizero ( see src/XZskms.hh )
.
This is an attempt of a tutorial on using #kms to set a video mode. I got interested in this topic when I realized how many dependencies an #opengl interface from within #X generates. The classic alternative, to for example #freeglut or #glfw or plain X interfacing, is #svgalib , which bears the disadvantage of required superuser privileges. This is unacceptable from a game development point of view. With the advent of KMS (Kernel Mode Setting, since #linux 2.6.29) another possibility opened: #libdrm . Low level access to video hardware usually sounds like an undesirable amount of work. Here this was just the case due to the sparse documentation of the #drm interface; the coding is actually straight forward. Documenting my work here hopefully helps other developers to consider using kms as well as explaining some of the #drm functionality. Before I start with the code I want to thank +Jesse Barnes for his inspiring blog post ( http://virtuousgeek.org/blog/index.php/jbarnes/2011/10/31/writing_stanalone_programs_with_egl_and_ ) and his hints for sample sources. The following code is based upon them. Be advised that dabbeling in the depth of your video hardware might harm it and I will in no way be held accountable for consequential damages of any kind. I went to work pretty brutally on my #Intel #GMA though and I never had any problems.
So lets get started with the code. First we need to open the #dri device as follows:
fd = open("/dev/dri/card0",O_RDWR | O_CLOEXEC);
this will work from within X. Next, we acquire the drm resources, which will fail from within X.
resources = drmModeGetResources(fd);
To be able to restore everything after we are done, we obtain the current video mode and save the framebuffer identifier.
crtc = drmModeGetCrtc(fd,resources->crtcs[0]);
oid = crtc->buffer_id;
Now, we decide what connector we want to use. The connector is the physical connection from the graphics adapter to the display. To do this we cycle through all connectors and pick the working connector with the index given by conn.
for(i=0;i<resources->count_connectors;++i)
{
⠂connector = drmModeGetConnector(fd,resources->connectors[i]);
⠂if(connector==0 || conn--!=0) { continue; }
⠂if(connector->connection==DRM_MODE_CONNECTED && connector->count_modes>0) { break; }
⠂drmModeFreeConnector(connector);
}
Next, we need to choose the encoding our connector should use. This is the protocol of data encoding. We just want the first working encoder.
for(i=0;i<resources->count_encoders;++i)
{
⠂encoder = drmModeGetEncoder(fd,resources->encoders[i]);
⠂if(encoder==0) { continue; }
⠂if(encoder->encoder_id==connector->encoder_id) { break; }
⠂drmModeFreeEncoder(encoder);
}
Then we test for the desired video mode, more precisely its width and height in pixels (XRES,YRES), the number of bits-per-pixels is not important yet.
for(i=0;i<connector->count_modes;++i)
{
⠂mode = connector->modes[i];
⠂if( (mode.hdisplay==XRES) && (mode.vdisplay==YRES) ) { break; }
}
In case our requested mode is not found we can force drm to do by our bidding if we provide a mode ourselve. A mode is basically composed of a #modeline , the same used within X.
drmModeModeInfo mode = { clock,hdisplay,hsync_start,hsync_end,htotal,hskew,vdisplay,vsync_start,vsync_end,vtotal,vsync,vrefresh((1000*clock)/(htotal*vtotal)),flags,type,name };
Now we set up the #framebuffer . This is a slightly problematic part, since it seemed at first glance hardware specific, but I found the dumb framebuffer which should be abstract enough for our pruposes. We set off two ioctl's and map the resulting framebuffer pointer. Here we need the bits-per-pixels (BPP).
struct drm_mode_create_dumb dc = { YRES,XRES,BPP,0,0,0,0 };
drmIoctl(fd,DRM_IOCTL_MODE_CREATE_DUMB,&dc);
struct drm_mode_map_dumb dm = { dc.handle,0,0 };
drmIoctl(fd,DRM_IOCTL_MODE_MAP_DUMB,&dm);
front = (long*)mmap(0,dc.size,PROT_READ | PROT_WRITE, MAP_SHARED,fd,dm.offset);
Finally, we enlist this framebuffer and then set the video mode.
drmModeAddFB(fd,XRES,YRES,BPP,BPP,dc.pitch,dc.handle,&id);
drmModeSetCrtc(fd,encoder->crtc_id,id,0,0,&connector->connector_id,1,&mode);
The framebuffer (here named front) can be used now and its contents are displayed by flushing it as follows.
drmModeDirtyFB(fd,id,0,0);
That is it. Furthermore I posted a full code example (kms.cc) at:
http://pastebin.com/UFzr2gQ5
this #c++ code can be compiled with
g++ kms.cc -okms -ldrm -I/usr/include/libdrm
and should be used from a pure terminal, by switching with STRG+ALT+x to TTYx with for example x = 1. There are two commandline options for this program. First a boolean to set wether a mode shall be forced or not; second, an integer being the index of the connector that shall be used. How to restore the original state please see the code sample. To see how I use kms please see:
http://github.com/atcl/xizero ( see src/XZskms.hh )
.