// 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 "types.h" #include "user.h" #include "fcntl.h" #include "fb.h" #define PROT_READ 0x1 #define PROT_WRITE 0x2 #define MAP_SHARED 0x01 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; }