├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── driver.c ├── fbe.c ├── get_fb_info_test.c └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | driver 3 | get_fb_info_test 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:23.04 2 | 3 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential libfuse-dev libsdl2-dev 4 | 5 | WORKDIR /root/code 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: driver get_fb_info_test 2 | 3 | driver: driver.c 4 | $(CC) -g -o driver driver.c -lfuse `sdl2-config --cflags --libs` 5 | 6 | get_fb_info_test: get_fb_info_test.c 7 | $(CC) -g -o get_fb_info_test get_fb_info_test.c 8 | 9 | build_docker_image: 10 | docker build -t fuse_fb_build_image . 11 | 12 | docker_shell: 13 | docker run -v.:/root/code -it fuse_fb_build_image 14 | 15 | build_inside_docker: 16 | docker run -v.:/root/code -it fuse_fb_build_image make 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FuseFB 2 | 3 | __FuseFB__ uses [FUSE](https://en.wikipedia.org/wiki/Filesystem_in_Userspace) low level block API __CUSE__ to simulate a [Linux Framebuffer](https://en.wikipedia.org/wiki/Linux_framebuffer) (like /dev/fb0) 4 | 5 | The file fbe.c is a copy from http://sixpak.org/fbe/ 6 | 7 | Fisrt thing you need to make sure you have is the kernel module cuse, you can check running lsmod | grep cuse 8 | 9 | For now you can start the "Driver" and use the test program 'get_fb_info_test' like this: 10 | 11 | ````bash 12 | $ make 13 | $ sudo ./driver & 14 | $ sudo ./get_fb_info_test /dev/fb_fuse_buffer 15 | ```` 16 | 17 | The same program also works with the native framebuffer: 18 | 19 | ````bash 20 | $ sudo ./get_fb_info_test /dev/fb0 21 | ```` 22 | -------------------------------------------------------------------------------- /driver.c: -------------------------------------------------------------------------------- 1 | #define FUSE_USE_VERSION 30 2 | #define _FILE_OFFSET_BITS 64 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #define LOG(...) do { fprintf(stderr, __VA_ARGS__); puts(""); } while (0) 14 | 15 | #define FB_WIDTH 800 16 | #define FB_HEIGHT 600 17 | 18 | SDL_Surface *screenSurface; 19 | 20 | static void 21 | cusetest_open(fuse_req_t req, struct fuse_file_info *fi) 22 | { 23 | LOG("open"); 24 | SDL_Window *window; 25 | 26 | SDL_Init(SDL_INIT_VIDEO); 27 | 28 | window = SDL_CreateWindow("FUSE FB", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, FB_WIDTH, FB_HEIGHT, SDL_WINDOW_SHOWN); 29 | screenSurface = SDL_GetWindowSurface(window); 30 | SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0, 0, 0xff)); 31 | SDL_UpdateWindowSurface(window); 32 | 33 | //SDL_Delay(5000); 34 | 35 | //SDL_FreeSurface(screenSurface); 36 | //SDL_DestroyWindow(window); 37 | //SDL_Quit(); 38 | 39 | fuse_reply_open(req, fi); 40 | } 41 | 42 | static void 43 | cusetest_read(fuse_req_t req, size_t size, off_t off, 44 | struct fuse_file_info *fi) 45 | { 46 | LOG("read"); 47 | fuse_reply_buf(req, "Hello", size > 5 ? 5 : size); 48 | } 49 | 50 | static void 51 | cusetest_write(fuse_req_t req, const char *buf, size_t size, 52 | off_t off, struct fuse_file_info *fi) 53 | { 54 | LOG("write (%zu bytes)", size); 55 | fuse_reply_write(req, size); 56 | } 57 | 58 | static void 59 | cusetest_ioctl(fuse_req_t req, int cmd, void *arg, 60 | struct fuse_file_info *fi, unsigned flags, 61 | const void *in_buf, size_t in_bufsz, size_t out_bufsz) 62 | { 63 | 64 | LOG("ioctl %d: insize: %zu outsize: %zu", cmd, in_bufsz, out_bufsz); 65 | switch (cmd) { 66 | case FBIOGET_FSCREENINFO: 67 | if (in_bufsz == 0){ 68 | struct iovec iov = {arg, sizeof(struct fb_fix_screeninfo)}; 69 | fuse_reply_ioctl_retry(req, &iov, 1, &iov, 1); 70 | } else { 71 | struct fb_fix_screeninfo fb_fix; 72 | fb_fix.line_length = FB_WIDTH; 73 | fb_fix.smem_len = 800 * 600 * screenSurface->format->BytesPerPixel; 74 | fb_fix.smem_start = (unsigned long int) screenSurface->pixels; 75 | strcpy(fb_fix.id, "Fuse FB"); 76 | fuse_reply_ioctl(req, 0, &fb_fix, sizeof(struct fb_fix_screeninfo)); 77 | } 78 | break; 79 | case FBIOGET_VSCREENINFO: 80 | if (in_bufsz == 0){ 81 | struct iovec iov = {arg, sizeof(struct fb_var_screeninfo)}; 82 | fuse_reply_ioctl_retry(req, &iov, 1, &iov, 1); 83 | } else { 84 | struct fb_var_screeninfo fb_var; 85 | fb_var.xres = FB_HEIGHT; 86 | fb_var.yres = FB_WIDTH; 87 | fb_var.xres_virtual = FB_HEIGHT; 88 | fb_var.yres_virtual = FB_WIDTH; 89 | fb_var.bits_per_pixel = screenSurface->format->BitsPerPixel; 90 | fuse_reply_ioctl(req, 0, &fb_var, sizeof(struct fb_var_screeninfo)); 91 | } 92 | break; 93 | case 23: 94 | if (in_bufsz == 0) { 95 | struct iovec iov = { arg, sizeof(int) }; 96 | fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); 97 | } else { 98 | LOG(" got value: %d", *((int *) in_buf)); 99 | fuse_reply_ioctl(req, 0, NULL, 0); 100 | } 101 | break; 102 | case 42: 103 | if (out_bufsz == 0) { 104 | struct iovec iov = { arg, sizeof(int) }; 105 | fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); 106 | } else { 107 | LOG(" write back value"); 108 | int v = 42; 109 | fuse_reply_ioctl(req, 0, &v, sizeof(int)); 110 | } 111 | break; 112 | } 113 | } 114 | 115 | static const struct cuse_lowlevel_ops cusetest_clop = { 116 | .open = cusetest_open, 117 | .read = cusetest_read, 118 | .write = cusetest_write, 119 | .ioctl = cusetest_ioctl, 120 | }; 121 | 122 | int 123 | main(int argc, char **argv) 124 | { 125 | // -f: run in foreground, -d: debug ouput 126 | // Compile official example and use -h 127 | const char *cusearg[] = { "test", "-f", "-d" }; // -d for debug -s for single thread 128 | const char *devarg[] = { "DEVNAME=fb_fuse_buffer" }; 129 | struct cuse_info ci; 130 | memset(&ci, 0x00, sizeof(ci)); 131 | ci.flags = CUSE_UNRESTRICTED_IOCTL; 132 | ci.dev_info_argc = 1; 133 | ci.dev_info_argv = devarg; 134 | 135 | return cuse_lowlevel_main(3, (char **) &cusearg, &ci, &cusetest_clop, 136 | NULL); 137 | } 138 | -------------------------------------------------------------------------------- /fbe.c: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id: fbe.c,v 1.4 2000/12/03 12:25:47 tgkamp Exp $ 3 | * 4 | * picoTK generic Frame Buffer Emulator 5 | * 6 | * Copyright (c) 2000 by Thomas Gallenkamp 7 | * Copyright (c) 2004 by Vince Busam 8 | * 9 | * 10 | * This program is free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 23 | * 24 | */ 25 | 26 | /* Credits: I used the X11/Xlib demo application xscdemo v2.2b 27 | * (c) 1992 by Sudarshan Karkada, as a starting point 28 | * for this program. 29 | */ 30 | 31 | #define PROGNAME "fbe" 32 | #define VERSION "0.02" 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | void X11_initialize(); 48 | void fbe_loop(); 49 | 50 | Display *display; 51 | Colormap colormap; 52 | GC gc; 53 | Window window, root, parent; 54 | int depth, screen, visibility; 55 | 56 | char *host = NULL, *cmapbuf; 57 | 58 | int CRTX=640; 59 | int CRTY=480; 60 | int ZOOM=1; 61 | int CRTX_TOTAL=640; 62 | int BITS_PER_PIXEL=8; 63 | int lcd_look=0; 64 | int redocmap = 0; 65 | 66 | int PIXELS_PER_BYTE; 67 | int PIXEL_MASK; 68 | int PIXELS_PER_LONG; 69 | 70 | #define MAX_CRTX 640 71 | #define MAX_CRTY 480 72 | 73 | unsigned long *crtbuf; 74 | 75 | #define CHUNKX 32 76 | #define CHUNKY 20 77 | 78 | unsigned long crcs[MAX_CRTX/CHUNKX][MAX_CRTY/CHUNKY]; 79 | 80 | unsigned long colors_24[256]= /* contains 24 bit (00rrggbb) rgb color 81 | mappings */ 82 | { 83 | 0x00000000,0x000000c0,0x0000c000,0x0000c0c0, 84 | 0x00c00000,0x00c000c0,0x00c0c000,0x00c0c0c0, 85 | 0x00808080,0x000000ff,0x0000ff00,0x0000ffff, 86 | 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff 87 | }; 88 | 89 | unsigned long colors_X11[256]; /* contains X11 variants, either 8,16 or 90 | 24 bits */ 91 | void fbe_setcolors(void) { 92 | int i; 93 | unsigned short *cmap = (unsigned short *)cmapbuf, c; 94 | for (i=0;i<256;i++) { 95 | c = cmap[i]; 96 | colors_24[i] = ((c&0xF800)<<8)|(((c>>5)&0x002F)<<10)|((c&0x001F)<<3); 97 | } 98 | if (BITS_PER_PIXEL==1) { 99 | if (lcd_look) { 100 | colors_24[0]=0x00a0c050; 101 | colors_24[1]=0x00808050; 102 | } else { 103 | colors_24[0]=0x00000000; 104 | colors_24[1]=0x00ff9000; 105 | } 106 | } 107 | } 108 | 109 | void fbe_calcX11colors(void) { 110 | int i; 111 | unsigned long c24; 112 | /* 113 | * Calculate X11 "pixel values" from 24 bit rgb (00rrggbb) color and 114 | * store them in colors_X11[} array for fast lookup. Mapping of rgb 115 | * colors to pixel values depends on the X11 Server color depth 116 | */ 117 | 118 | for (i=0; i<256; i++) { 119 | XColor xc; 120 | 121 | c24=colors_24[i]; 122 | 123 | xc.red= ((c24&0xff0000)>>16)*0x0101; 124 | xc.green= ((c24&0x00ff00)>> 8)*0x0101; 125 | xc.blue= ((c24&0x0000ff) )*0x0101; 126 | xc.flags= 0; 127 | XAllocColor(display,colormap,&xc); 128 | //XQueryColor(display,colormap,&xc); 129 | colors_X11[i]=xc.pixel; 130 | } 131 | } 132 | 133 | void fbe_init(void *crtbuf_) 134 | { 135 | 136 | if(host == NULL) 137 | if(( host = (char *) getenv("DISPLAY")) == NULL) 138 | { fprintf(stderr, "%s", "Error: No environment variable DISPLAY\n"); 139 | } 140 | 141 | PIXELS_PER_BYTE=(8/BITS_PER_PIXEL); 142 | PIXEL_MASK=((unsigned char *) 143 | "\x00\x01\x03\x07\x0f\x1f\x3f\x7f\xff") [BITS_PER_PIXEL]; 144 | PIXELS_PER_LONG=PIXELS_PER_BYTE*4; 145 | X11_initialize(); 146 | 147 | gc = XCreateGC(display, window, 0, NULL); 148 | crtbuf=crtbuf_; 149 | fbe_setcolors(); 150 | fbe_calcX11colors(); 151 | fbe_loop(); 152 | } 153 | 154 | 155 | void X11_initialize() 156 | { 157 | XSetWindowAttributes attr; 158 | char name[80]; 159 | Pixmap iconPixmap; 160 | XWMHints xwmhints; 161 | 162 | if((display = XOpenDisplay(host)) == NULL) 163 | { 164 | fprintf(stderr,"Error: Connection could not be made.\n"); 165 | exit(1); 166 | } 167 | 168 | screen = DefaultScreen(display); 169 | colormap=DefaultColormap(display,screen); 170 | parent = root = RootWindow(display,screen); 171 | depth = DefaultDepth(display,screen); 172 | 173 | XSelectInput(display,root,SubstructureNotifyMask); 174 | 175 | attr.event_mask = ExposureMask; 176 | 177 | attr.background_pixel=BlackPixel(display,screen); 178 | 179 | { 180 | window = XCreateWindow(display,root,0, 181 | 0, 182 | CRTX*ZOOM,CRTY*ZOOM, 183 | 0,depth,InputOutput, DefaultVisual(display,screen), 184 | CWEventMask|CWBackPixel,&attr); 185 | 186 | sprintf(name,"fbe %d * %d * %d bpp",CRTX,CRTY,BITS_PER_PIXEL); 187 | 188 | XChangeProperty(display,window,XA_WM_NAME,XA_STRING,8, 189 | PropModeReplace,name,strlen(name)); 190 | XMapWindow(display,window); 191 | } 192 | 193 | xwmhints.icon_pixmap = iconPixmap; 194 | xwmhints.initial_state = NormalState; 195 | xwmhints.flags = IconPixmapHint | StateHint; 196 | 197 | XSetWMHints(display, window, &xwmhints ); 198 | XClearWindow(display,window); 199 | XSync(display, 0); 200 | 201 | } 202 | 203 | 204 | unsigned long calc_patch_crc(int ix, int iy) { 205 | unsigned long crc; 206 | int x, y; 207 | int off; 208 | 209 | off=(ix*CHUNKX)/PIXELS_PER_LONG+iy*CHUNKY*(CRTX_TOTAL/PIXELS_PER_LONG); 210 | crc=0x8154711; 211 | 212 | for (x=0; x>( ((PIXELS_PER_BYTE-1)-(x&(PIXELS_PER_BYTE-1)))*BITS_PER_PIXEL)) 249 | & PIXEL_MASK; 250 | XSetForeground(display,gc,colors_X11[color]); 251 | if (ZOOM>1) 252 | XFillRectangle(display, pixmap, gc, x*ZOOM, y*ZOOM, 2, 2); 253 | else 254 | XDrawPoint(display, pixmap, gc, x, y); 255 | } 256 | 257 | XCopyArea(display, pixmap, window, gc, 0, 0, CHUNKX*ZOOM, 258 | CHUNKY*ZOOM, ix*CHUNKX*ZOOM, iy*CHUNKY*ZOOM); 259 | crcs[ix][iy]=crc; 260 | } 261 | 262 | 263 | void fbe_loop() 264 | { 265 | pixmap=XCreatePixmap(display,window,CHUNKX*ZOOM,CHUNKY*ZOOM,depth); 266 | repaint=0; 267 | while(1) { 268 | int x; 269 | int y; 270 | repaint=0; 271 | /* 272 | Check if to force complete repaint because of window 273 | expose event 274 | */ 275 | while((XPending(display) > 0)) { 276 | XEvent event; 277 | XNextEvent(display,&event); 278 | if (event.type==Expose) repaint=1; 279 | } 280 | 281 | /* 282 | Sample all chunks for changes in shared memory buffer and 283 | eventually repaint individual chunks. Repaint everything if 284 | repaint is true (see above) 285 | */ 286 | for (y=0; y]\n" 373 | " Options:\n" 374 | " -x X size [%3d]\n" 375 | " -y Y Size [%3d]\n" 376 | " -t X Total Size [%3d]\n" 377 | " -d Color depths bpp [%d] \n" 378 | " -z Zoom factor [%d] \n" 379 | " -l LCD look for 1bpp \n", 380 | CRTX,CRTY,CRTX_TOTAL,BITS_PER_PIXEL,ZOOM); 381 | return(1); } 382 | 383 | /* 384 | * Create shared memory dummy file if not already existent 385 | */ 386 | fd=open("/tmp/fbe_buffer",O_RDONLY); 387 | if (fd>0) { 388 | close(fd); 389 | } else { 390 | fd=open("/tmp/fbe_buffer",O_CREAT|O_WRONLY,0777); 391 | for(i=0; i<(CRTX_TOTAL*CRTY*BITS_PER_PIXEL/8); i++) write(fd,"\000",1); 392 | close(fd); 393 | } 394 | 395 | fd=open("/tmp/fbe_buffer",O_RDWR); 396 | fbuf=mmap(NULL, (CRTX_TOTAL*CRTY*BITS_PER_PIXEL/8), PROT_READ|PROT_WRITE, MAP_SHARED,fd,0); 397 | 398 | cfd=open("/tmp/fbe_cmap",O_RDONLY); 399 | if (cfd>0) { 400 | close(cfd); 401 | } else { 402 | cfd=open("/tmp/fbe_cmap",O_CREAT|O_WRONLY,0777); 403 | for(i=0; i<512; i++) write(cfd,"\000",1); 404 | close(cfd); 405 | } 406 | 407 | cfd=open("/tmp/fbe_cmap",O_RDWR); 408 | cmapbuf=mmap(NULL, 512, PROT_READ|PROT_WRITE, MAP_SHARED,cfd,0); 409 | signal(SIGUSR1,usr1_handler); 410 | 411 | fp = fopen("/tmp/fbe.pid","w"); 412 | if (fp) { 413 | sprintf(buf,"%d",getpid()); 414 | fputs(buf,fp); 415 | fclose(fp); 416 | } 417 | 418 | fbe_init(fbuf); 419 | 420 | return 0; 421 | } 422 | -------------------------------------------------------------------------------- /get_fb_info_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define FB "/dev/fbe_buffer" 15 | 16 | static int fb; 17 | static struct fb_fix_screeninfo fix; 18 | static struct fb_var_screeninfo var; 19 | 20 | int main(int argc, char** argv) 21 | { 22 | unsigned int *fp; 23 | int res; 24 | 25 | fb = open(argv[1], O_RDWR); 26 | if (fb == -1) { 27 | fprintf(stderr, "%s %s: %s\n", "Unable to open fb", argv[1], strerror(errno)); 28 | return 1; 29 | } 30 | res = ioctl(fb, FBIOGET_FSCREENINFO, &fix); 31 | if (res != 0) { 32 | perror("FSCREENINFO failed"); 33 | close(fb); 34 | return 1; 35 | } 36 | 37 | 38 | var.xres_virtual = 10; 39 | res = ioctl(fb, FBIOGET_VSCREENINFO, &var); 40 | if (res != 0) { 41 | perror("VSCREENINFO failed"); 42 | close(fb); 43 | return 1; 44 | } 45 | 46 | printf("Framebuffer Id is: %s\n", fix.id); 47 | printf("size %dx%d @ %d bits per pixel\n", var.xres_virtual, var.yres_virtual, var.bits_per_pixel); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | int fd = open("/dev/cusetest", O_RDWR); 9 | 10 | const char* msg = "Fooooo"; 11 | write(fd, msg, strlen(msg)); 12 | 13 | int v = 63; 14 | ioctl(fd, 23, &v); 15 | fprintf(stderr, "value is now: %d\n", v); 16 | ioctl(fd, 42, &v); 17 | fprintf(stderr, "value is now: %d\n", v); 18 | close(fd); 19 | return 0; 20 | } 21 | 22 | --------------------------------------------------------------------------------