An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
219 lines
6.5 KiB
C
219 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 "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;
|
|
}
|