Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
203 lines
6.5 KiB
C
203 lines
6.5 KiB
C
// /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 "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "gfx/gfx.h"
|
|
#include "core/param.h"
|
|
#include "arch/mmu.h"
|
|
#include "core/proc.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/file.h"
|
|
#include "gfx/fb.h"
|
|
#include "core/uaccess.h"
|
|
#include "uapi/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;
|
|
}
|