SchOS/cmd/fbinfo/main.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

217 lines
6.5 KiB
C

// fbinfo -- ask /dev/fb0 what it is, and optionally draw on it.
//
// This exists because the framebuffer is the one device whose correctness you
// cannot check by reading kernel source: the numbers have to come back from
// the hardware the bootloader actually programmed. Three places in the kernel
// used to assume the framebuffer lived at 0xfd000000 and was 800x600x32; they
// agreed with QEMU's stdvga by luck, and nothing would have told us when the
// luck ran out. Now `fbinfo` would.
//
// fbinfo print the geometry
// fbinfo fill draw a gradient over the whole screen through mmap
// fbinfo bars draw colour bars through write(), no mmap involved
//
// The two drawing modes take different paths on purpose. If `fill` works and
// `bars` does not, the mapping is right and the device's read/write is wrong;
// if it is the other way round, the reverse. Having both makes a failure say
// which half broke.
#include "core/types.h"
#include "uapi/user.h"
#include "uapi/fcntl.h"
#include "gfx/fb.h"
#include "uapi/posix.h" /* PROT_*, MAP_* -- one definition, shared */
static void
puts(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void*)s, n);
}
// Unsigned decimal. No printf in lib/, and pulling one in for six numbers
// would be a worse trade than fourteen lines.
static void
putu(unsigned long v)
{
char buf[24];
int i = 0;
if (v == 0) {
write(1, "0", 1);
return;
}
while (v > 0 && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
while (i > 0) {
i--;
write(1, &buf[i], 1);
}
}
static void
puthex(unsigned long v)
{
static const char d[] = "0123456789abcdef";
char buf[16];
int i = 0;
write(1, "0x", 2);
if (v == 0) {
write(1, "0", 1);
return;
}
while (v > 0 && i < (int)sizeof(buf)) {
buf[i++] = d[v & 0xf];
v >>= 4;
}
while (i > 0) {
i--;
write(1, &buf[i], 1);
}
}
// Name, then spaces out to a fixed column. Padding inside each string
// literal is what produced "bits_per_pixel32": the longest name happened to
// be exactly as wide as the column, so it got no separator at all and the
// value ran straight into it.
static void
label(const char *name)
{
int n = 0;
while (name[n]) n++;
write(1, (void*)name, n);
while (n < 16) { write(1, " ", 1); n++; }
}
static void
field(const char *name, unsigned long v)
{
label(name);
putu(v);
puts("\n");
}
static void
chanline(const char *name, struct fb_bitfield *b)
{
label(name);
puts("offset ");
putu(b->offset);
puts(", length ");
putu(b->length);
if (b->length == 0)
puts(" (absent)");
puts("\n");
}
int
main(int argc, char *argv[])
{
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
int fd;
fd = open("/dev/fb0", O_RDWR);
if (fd < 0) {
puts("fbinfo: cannot open /dev/fb0\n");
return 1;
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &var) < 0) {
puts("fbinfo: FBIOGET_VSCREENINFO failed\n");
return 1;
}
if (ioctl(fd, FBIOGET_FSCREENINFO, &fix) < 0) {
puts("fbinfo: FBIOGET_FSCREENINFO failed\n");
return 1;
}
if (argc < 2) {
label("id");
puts(fix.id);
puts("\n");
field("xres", var.xres);
field("yres", var.yres);
field("bits_per_pixel", var.bits_per_pixel);
label("smem_start");
puthex(fix.smem_start);
puts("\n");
field("smem_len", fix.smem_len);
field("line_length", fix.line_length);
// The stride is the number worth staring at. A card is free to pad
// each row, and a program that computes row starts as
// y * xres * bytes_per_pixel instead of y * line_length draws a
// picture that shears progressively down the screen.
if (fix.line_length != var.xres * (var.bits_per_pixel / 8)) {
label("note");
puts("stride is padded; rows are not xres*bpp/8 apart\n");
}
chanline("red", &var.red);
chanline("green", &var.green);
chanline("blue", &var.blue);
chanline("transp", &var.transp);
return 0;
}
if (argv[1][0] == 'f') { // fill
unsigned char *fb = mmap(0, fix.smem_len,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (fb == 0 || (long)fb < 0) {
puts("fbinfo: mmap failed\n");
return 1;
}
unsigned bpp = var.bits_per_pixel / 8;
for (unsigned y = 0; y < var.yres; y++) {
unsigned char *row = fb + (unsigned long)y * fix.line_length;
for (unsigned x = 0; x < var.xres; x++) {
unsigned r = (x * 255) / (var.xres ? var.xres : 1);
unsigned g = (y * 255) / (var.yres ? var.yres : 1);
unsigned long px = ((unsigned long)r << var.red.offset) |
((unsigned long)g << var.green.offset) |
((unsigned long)0x80 << var.blue.offset);
unsigned char *p = row + (unsigned long)x * bpp;
for (unsigned b = 0; b < bpp; b++)
p[b] = (unsigned char)(px >> (b * 8));
}
}
puts("fbinfo: gradient drawn through mmap\n");
return 0;
}
if (argv[1][0] == 'b') { // bars
// One scanline at a time through write(). No mapping: this is the
// path that used to be devsw's nullwrite, i.e. a silent no-op.
static unsigned char row[8192];
unsigned bpp = var.bits_per_pixel / 8;
unsigned len = fix.line_length;
if (len > sizeof(row))
len = sizeof(row);
for (unsigned y = 0; y < var.yres; y++) {
unsigned band = (y * 8) / (var.yres ? var.yres : 1);
unsigned long px =
((unsigned long)((band & 1) ? 255 : 0) << var.red.offset) |
((unsigned long)((band & 2) ? 255 : 0) << var.green.offset) |
((unsigned long)((band & 4) ? 255 : 0) << var.blue.offset);
for (unsigned x = 0; x * bpp + bpp <= len; x++)
for (unsigned b = 0; b < bpp; b++)
row[x * bpp + b] = (unsigned char)(px >> (b * 8));
if (write(fd, row, len) != (int)len) {
puts("fbinfo: short write to /dev/fb0\n");
return 1;
}
}
puts("fbinfo: colour bars drawn through write()\n");
return 0;
}
puts("usage: fbinfo [fill|bars]\n");
return 1;
}