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
217 lines
6.5 KiB
C
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;
|
|
}
|