一.QEMU上创建LCD设备仿真
\qemu\hw\display\100ask_qemu_fb.c
1.在QEMU上创建LCD设备
static const TypeInfo ask100fb_info = {
.name = TYPE_100ASKFB,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(ASK100FbState), /* object_new_with_type 创建设备时会malloc该结构体, 并传给instance_init */
.class_init = ask100fb_class_init,
.instance_init = ask100fb_init, /* object_init_with_type */
};
void create_100ask_qemu_fb(void)
{
DeviceState *dev;
dev = qdev_create(NULL, TYPE_100ASKFB);
qdev_init_nofail(dev);
}
static void ask100fb_register_types(void)
{
type_register_static(&ask100fb_info);
}
type_init(ask100fb_register_types)
2.给LCD控制器提供回调函数
ask100fb_class_init设备类初始化实现图像更新,ask100fb_init设备实例初始化实现读写lcd驱动的值。
// 设备类初始化回调函数
static void ask100fb_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
dc->realize = ask100fb_realize; /* ask100fb_realize 何时被调用 */
/* Note: This device does not any state that we have to reset or migrate */
}
static void ask100fb_realize(DeviceState *dev, Error **errp)
{
ASK100FbState *s = ASK100FB(dev);
if (imx6ul_board_ui_backgroud_prepare())
return;
s->invalidate = 1;
s->fb_xres = imx6ul_board_descs[selected_board].lcd.w;
s->fb_yres = imx6ul_board_descs[selected_board].lcd.h;
s->board_xres = board_mem_pixels.iWidth;
s->board_yres = board_mem_pixels.iHeight;
dev->id = "board";
s->con = graphic_console_init(dev, 0, &ask100fb_ops, s); // 调用ops回调函数实现板子画面更新
qemu_console_resize(s->con, s->board_xres, s->board_yres);
s->input_state = qemu_input_handler_register(dev, &touchscreen_handler);
qemu_input_handler_bind(s->input_state, dev->id, 0, NULL);
qemu_input_handler_activate(s->input_state);
}
static const GraphicHwOps ask100fb_ops = {
.invalidate = ask100fb_invalidate,
.gfx_update = ask100fb_update,
};
static void ask100fb_update(void *opaque)
{
ASK100FbState *s = ASK100FB(opaque);
SysBusDevice *sbd;
DisplaySurface *surface = qemu_console_surface(s->con);
static int inited = 0;
int dest_width;
int src_width;
int first = 0;
int last = 0;
int fb_x, fb_y;
src_width = s->fb_xres * s->fb_bpp / 8;
dest_width = s->board_xres * surface_bits_per_pixel(surface) / 8;
sbd = SYS_BUS_DEVICE(s);
if (inited)
{
imx_gpio_ui_update(opaque);
if (!s->fb_base_phys)
return;
//if (s->invalidate) {
framebuffer_update_memory_section(&s->fbsection, sysbus_address_space(sbd), s->fb_base_phys,
s->fb_yres, src_width);
//}
framebuffer_update_display(surface, &s->fbsection, s->fb_xres, s->fb_yres,
src_width, dest_width, 0, 1, ask100fb_draw_line_src16,
s, &first, &last); // 更新LCD要显示图片
fb_x = imx6ul_board_descs[selected_board].lcd.x;
fb_y = imx6ul_board_descs[selected_board].lcd.y;
dpy_gfx_update(s->con, fb_x, fb_y, s->fb_xres, s->fb_yres); // 更新板子及LCD的图像
//}
}
else
{
framebuffer_update_region(surface, &board_mem_pixels, 0, 0, s->board_xres, s->board_yres); // 更新板子图片
dpy_gfx_update(s->con, 0, 0, s->board_xres, s->board_yres); // 首次显示只显示板子图片
imx_gpio_ui_init();
inited = 1;
}
s->invalidate = 0;
}
// lcd设备实例初始化
static void ask100fb_init(Object *obj)
{
ASK100FbState *s = ASK100FB(obj);
MemoryRegion *address_space = get_system_memory();
memory_region_init_io(&s->iomem, obj, &ask100_lcdc_ops, s, TYPE_100ASKFB, 16);
memory_region_add_subregion(address_space, FSL_IMX6UL_LCDIF_ADDR, &s->iomem);
//(void)ask100_touchscreen_ops;
memory_region_init_io(&s->iomem_touchscreen, obj, &ask100_touchscreen_ops, s, "100ask_qemu_touchscreen", 16);
memory_region_add_subregion(address_space, FSL_IMX6UL_TOUCHSCREEN_ADDR, &s->iomem_touchscreen);
}
static const MemoryRegionOps ask100_lcdc_ops = {
.read = ask100_lcdc_read,
.write = ask100_lcdc_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static uint64_t ask100_lcdc_read(void *opaque, hwaddr addr,
unsigned size)
{
ASK100FbState *s = ASK100FB(opaque);
switch (addr) {
case 0x00:
return s->fb_base_phys;
case 0x04:
return s->fb_xres;
case 0x08:
return s->fb_yres;
case 0x0c:
return s->fb_bpp;
default:
break;
}
return 0;
}
// lcd驱动写数据时,此函数被调用
static void ask100_lcdc_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
ASK100FbState *s = ASK100FB(opaque);
switch (addr) {
case 0x00:
s->fb_base_phys = value;
break;
case 0x04:
s->fb_xres = value;
//qemu_console_resize(s->con, s->fb_xres, s->fb_yres);
break;
case 0x08:
s->fb_yres = value;
//qemu_console_resize(s->con, s->fb_xres, s->fb_yres);
break;
case 0x0c:
s->fb_bpp = value;
break;
default:
break;
}
}
二.LCD设备驱动
QEMU自带/dev/fb0,也可以自己编写驱动模块。
既然操作的不是真实的 LCD 控制器,那么 LCD 驱动程序可以极大精简。
① 对于 LCD 控制器,只需要操作 4 个寄存器:
分别用来保存:framebuffer 的物理地址、宽度、高度、BPP。
你需要记住这些寄存器的物理地址(可以自己指定地址是什么)。
② 对于 FrameBuffer:
驱动程序分配得到 FrameBuffer 后,要把它的物理地址写到上述第 1 个寄存器里。
部分代码如下,其他的时钟使能、GPIO 设置等等都不再需要:
三.应用层写LCD
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/time.h>
#include "fb.h"
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#define ALLOW_OS_CODE 1
/*#include "../rua/include/rua.h"*/
#if 0
#define DEB(f) (f)
#else
#define DEB(f)
#endif
typedef unsigned int u32;
typedef unsigned short u16;
typedef u32 color_t;
typedef unsigned char RMuint8;
typedef unsigned short RMuint16;
typedef unsigned int RMuint32;
struct fb_var_screeninfo fb_var;
struct fb_fix_screeninfo fb_fix;
char * fb_base_addr = NULL;
struct ldm_info {
int fd;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
size_t pixel_size;
size_t x, y; //通过fix成员计算得到的实际分辨率
size_t fb_size; //显存大小
size_t addr;
/* @x, y: 像素点的坐标
* @color: 不管屏幕是什么色深,该参数都是标准RGB888的32位色的颜色值
*/
void (*draw_pixel)(struct ldm_info *, size_t x, size_t y, color_t color);
};
struct files {
size_t offset;
};
struct infos {
size_t width;
size_t height;
size_t count;
size_t sizeimage;
};
struct bmp_head {
struct files file;
struct infos info;
};
/* Set a pixel color
* @param p_osd osd descriptor
* @param x
* @param y
* @color 0xAARRGGBB (AA = alpha : 0 transparent)
*/
static void set_pixel(RMuint32 x, RMuint32 y, RMuint32 color)
{
/*static RMuint32 i=0;*/
/* TODO We assume for now we have contigus regions */
switch (fb_var.bits_per_pixel){
case 32:
{
RMuint32 *addr= (RMuint32 *)fb_base_addr+(y*fb_var.xres+x);
*addr = color;
}
break;
case 24:
{
/*RMuint32 *addr= (RMuint32 *)fb_base_addr+(y*fb_var.xres+x);
*addr = (0x00FFFFFF) & color;*/
RMuint8 *addr = (RMuint8 *)fb_base_addr+(y*fb_var.xres+x)*3;
*addr = (RMuint8) (color & 0xFF);
*(addr+1) = (RMuint8) (color >> 8 & 0xFF);
*(addr+2) = (RMuint8) (color >> 16 & 0xFF);
}
break;
case 16:
{
RMuint16 *addr = (RMuint16 *) fb_base_addr+(y*fb_var.xres+x);
*addr = (RMuint16) color;
}
break;
case 8:
{
RMuint8 *addr = (RMuint8 *)fb_base_addr+(y*fb_var.xres+x);
*addr = (RMuint8) color;
}
break;
case 4:
{
RMuint8 *addr = (RMuint8 *)fb_base_addr+(y*fb_var.xres+x) / 2;
RMuint32 bit = (RMuint32) ((RMuint8 *)fb_base_addr+(y*fb_var.xres+x) % 2);
RMuint8 pixel = *addr;
pixel = pixel & ( 0x0F << bit * 4 );
color = ( color & 0x0000000F ) << 4;
*addr = pixel | ( (RMuint8) color >> bit * 4 );
}
break;
case 2:
{
RMuint8 *addr = (RMuint8 *)fb_base_addr+(y*fb_var.xres+x) / 4;
RMuint32 bit = (RMuint32) ((RMuint8 *)fb_base_addr+(y*fb_var.xres+x) % 4);
RMuint8 pixel = *addr;
pixel = pixel & ( 0xFF ^ ( 0x3 << bit * 2));
color = color & 0x00000003;
*addr = pixel | ( (RMuint8) color << bit * 2 );
}
break;
case 1:
{
/*RMuint8 *addr = (RMuint8 *)fb_base_addr+(y*fb_var.xres+x);
*addr = (RMuint8) color;*/
RMuint8 *addr = (RMuint8 *)fb_base_addr+(y*fb_var.xres+x) / 8;
RMuint32 bit = (RMuint32) ((RMuint8 *)fb_base_addr+(y*fb_var.xres+x) % 8);
RMuint8 pixel = *addr;
pixel = pixel & ( 0xFF ^ ( 0x1 << bit ));
color = color & 0x00000001;
*addr = pixel | ( (RMuint8) color << bit );
}
break;
default:
fprintf(stderr,"Unknown bpp : %d\n",fb_var.bits_per_pixel);
break;
}
/*if (i<10){
DEB(fprintf(stderr,"(%ld,%ld) [%p] <- %lX\n",x,y,addr,*addr));
i++;
}*/
}
static void mire()
{
RMuint32 x,y;
RMuint32 color;
RMuint8 red,green,blue,alpha;
DEB(fprintf(stderr,"begin mire\n"));
for (y=0;y<fb_var.yres;y++)
for (x=0;x<fb_var.xres;x++){
color = ((x-fb_var.xres/2)*(x-fb_var.xres/2) + (y-fb_var.yres/2)*(y-fb_var.yres/2))/64;
red = (color/8) % 256;
green = (color/4) % 256;
blue = (color/2) % 256;
alpha = (color*2) % 256;
/*alpha = 0xFF;*/
color |= ((RMuint32)alpha << 24);
color |= ((RMuint32)red << 16);
color |= ((RMuint32)green << 8 );
color |= ((RMuint32)blue );
set_pixel(x,y,color);
}
DEB(fprintf(stderr,"end mire\n"));
}
int main(int argc, char** argv)
{
int fd = 0;
long int screensize = 0;
unsigned int width = 0;
unsigned int height = 0;
unsigned int bpp = 0;
unsigned int *pdwAddr = 0;
unsigned short *pwAddr = 0;
unsigned char *pcAddr = 0;
unsigned int dwOffset;
unsigned int dwVal;
unsigned int num;
int i;
if (argc < 2) {
printusage(argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR);
if (fd <0){
printf("error opening %s\n",argv[1]);
exit(1);
}
/* Get fixed screen information */
if (ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix)) {
printf("Error reading fb fixed information.\n");
exit(1);
}
/* Get variable screen information */
if (ioctl(fd, FBIOGET_VSCREENINFO, &fb_var)) {
printf("Error reading fb variable information.\n");
exit(1);
}
if ((argc == 2) || (argc > 2 && !strcmp(argv[2], "set")))
{
/*blank */
memset(fb_base_addr,0x00,screensize);
usleep(50000);
mire();
}
return 0;
}
实际效果
[root@qemu_imx6ul:~]# myfb-test /dev/fb0