// /dev/fb0 -- the linear framebuffer as a file. // // Until now this node existed but was wired to major 2, whose devsw entry was // { nullread, nullwrite }: reads returned end-of-file and writes were thrown // away, silently. The only thing that actually worked was mmap, and only // because sys_mmap special-cased the major number. // // Reads and writes here address the framebuffer by byte offset, so lseek // positions the "cursor" and a whole frame can be written with one write(). // That is slower than mmap and always will be -- it is a copy -- but it is // what makes the device inspectable from a shell, which is worth a great deal // while there is no GUI yet to inspect it with. #include "types.h" #include "defs.h" #include "gfx.h" #include "param.h" #include "mmu.h" #include "proc.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "file.h" #include "fb.h" #include "uaccess.h" #include "errno.h" // Describe one colour channel. A channel that does not exist gets length 0, // which is how callers are supposed to detect "no alpha" rather than by // assuming from bits_per_pixel. static void chan(struct fb_bitfield *f, unsigned int offset, unsigned int length) { f->offset = offset; f->length = length; f->msb_right = 0; } // Pixel layout for the depths gfx_draw_pixel knows how to write. Keeping the // two in step matters: this is the kernel telling userspace how to build a // pixel, and if it disagrees with how the kernel builds one, every colour // drawn by a program is wrong in a way that looks like a hardware fault. static void pixel_format(struct fb_var_screeninfo *v, unsigned int bpp) { switch (bpp) { case 32: chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8); chan(&v->transp, 24, 0); // the byte is there; nothing honours it break; case 24: chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8); chan(&v->transp, 0, 0); break; case 16: // RGB565, matching the packing gfx_draw_pixel does for this depth. chan(&v->red, 11, 5); chan(&v->green, 5, 6); chan(&v->blue, 0, 5); chan(&v->transp, 0, 0); break; default: chan(&v->red, 0, 0); chan(&v->green, 0, 0); chan(&v->blue, 0, 0); chan(&v->transp, 0, 0); break; } } int fbread(struct inode *ip, uint64 off, char *dst, int n) { volatile uchar *fb = gfx_hw_ptr(); uint64 size = gfx_fb_size(); if (!fb) return -ENODEV; if (n < 0) return -EINVAL; if ((uint64)off >= size) return 0; // past the end of the screen: EOF if ((uint64)off + (uint64)n > size) n = (int)(size - (uint64)off); memmove(dst, (void*)(fb + off), n); return n; } // Set once something has drawn through this device, so that fbclose knows // whether the screen still belongs to the text console. Reading the // framebuffer, or asking it its geometry, does not count -- `fbinfo` with no // arguments must not wipe its own output off the screen. static int fb_dirty; // Called from sys_mmap when /dev/fb0 is mapped. A mapping is a licence to // draw, and the drawing happens with no further syscalls, so this is the last // point at which the kernel hears about it at all. void fb_mapped(void) { fb_dirty = 1; } int fbwrite(struct inode *ip, uint64 off, char *src, int n) { volatile uchar *fb = gfx_hw_ptr(); uint64 size = gfx_fb_size(); if (!fb) return -ENODEV; if (n < 0) return -EINVAL; if ((uint64)off >= size) return -ENOSPC; // a screen cannot be extended if ((uint64)off + (uint64)n > size) n = (int)(size - (uint64)off); memmove((void*)(fb + off), src, n); fb_dirty = 1; return n; } // The last descriptor on /dev/fb0 is going away. // // Whatever was drawing has finished, and the screen still holds its final // frame -- so the shell prompt underneath is invisible and the machine looks // hung. It is not: the process exited cleanly and the console is writing // happily into a framebuffer nobody can read any more. There is no character // backing store to repaint from (the console draws glyphs straight into the // framebuffer and scrolls with a memmove), so the honest recovery is to clear // and go home rather than to pretend the old text can be restored. void fbclose(struct inode *ip) { if (!fb_dirty) return; fb_dirty = 0; // Said out loud, because a screen clearing on its own is otherwise // indistinguishable from a bug -- and this is the only place in the system // that wipes the display without being asked to. cprintf("fb0: released by pid %d, console restored\n", myproc() ? myproc()->pid : -1); gfx_screen_reset(); } // FBIO* handling, called from sys_ioctl once it has established that the fd // refers to this device. `arg` is the raw user address; each request // validates it for the size it is actually going to touch, rather than for // the largest struct here -- fb_fix_screeninfo is half the size of // fb_var_screeninfo, and a buffer sitting near the end of a mapping is a // legitimate place to put the smaller one. int fb_ioctl(int request, addr_t arg) { int w, h; if (!gfx_fb_ptr()) return -ENODEV; gfx_dims(&w, &h); switch (request) { case FBIOGET_VSCREENINFO: { struct fb_var_screeninfo *v = (struct fb_var_screeninfo *)user_ptr(arg, sizeof(*v)); if (!v) return -EFAULT; memset(v, 0, sizeof(*v)); v->xres = v->xres_virtual = (unsigned int)w; v->yres = v->yres_virtual = (unsigned int)h; v->bits_per_pixel = gfx_fb_bpp(); v->activate = FB_ACTIVATE_NOW; pixel_format(v, v->bits_per_pixel); // height/width (physical millimetres) and every timing field stay zero: // VBE does not report them and a guess would be worse than an admission. return 0; } case FBIOGET_FSCREENINFO: { struct fb_fix_screeninfo *f = (struct fb_fix_screeninfo *)user_ptr(arg, sizeof(*f)); if (!f) return -EFAULT; memset(f, 0, sizeof(*f)); safestrcpy(f->id, "schosfb", sizeof(f->id)); f->smem_start = (unsigned long)gfx_fb_phys(); f->smem_len = (unsigned int)gfx_fb_size(); f->type = FB_TYPE_PACKED_PIXELS; f->visual = FB_VISUAL_TRUECOLOR; f->line_length = gfx_fb_stride(); return 0; } // The mode came from the bootloader's VBE call and we have no way to // change it. Saying so is more useful than accepting the request and // leaving the caller to discover, by drawing, that nothing happened. case FBIOPUT_VSCREENINFO: return -EINVAL; case FBIOBLANK: return -EINVAL; } return -ENOTTY; }