SchOS/kernel/dev/fb.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
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
2026-10-23 15:05:12 +02:00

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;
}