docs/plan/hybrid.md mechanisms 2 and 4: MMIO and DMA to a process.
A process can enumerate the bus, claim a device exclusively, map its BARs and
get memory the device can be pointed at. Proven end to end against an e1000e
-- the exact device class we cannot drive and would want a foreign driver for:
device 7: 8086:10d3 (network)
claimed
BAR0 at 0x81060000, 128 KB
regs[0..3] = 0x00140261 0x00000000 0x00080283 0x00000000
MMIO reaches the device
DMA 8 KB at virtual 0x40001000, physical 0x3f8eb000
Those are the e1000e's CTRL and STATUS with full-duplex and link-up set, which
is how you tell a real mapping from one reading 0xFFFFFFFF off an unmapped
address. Claiming a device the kernel drives is refused -- two writers of one
register set looks like hardware that has started lying -- and everything a
process held is released when it exits, because a driver that crashed must not
keep its hardware away from its replacement.
mmap's offset is the BAR's physical address, checked against the BARs of
devices *this process has claimed*. Without that check the device is /dev/mem
with extra steps and a process could map the kernel's own memory. Mappings are
write-through: a cached write to a doorbell arrives whenever the cache feels
like it, which from a driver's side is indistinguishable from hardware that
ignores you.
The interface is about PCI devices, not about any driver model, and its first
users will be SchOS's own drivers when virtio-gpu is evicted. That ordering is
the design rather than an accident: an interface whose first consumers are
native drivers is a general mechanism something foreign can use later, instead
of a hole shaped like something foreign.
Also: PROT_* and MAP_* moved to include/posix.h. They were defined only in
sys_mem.c, so cmd/fbinfo carried a private copy and this wanted a third --
two copies of a number the kernel compares against, where a mismatch shows up
as a mapping with the wrong permissions rather than an error.
No IOMMU, so this isolates a driver against its own bugs and not against its
own DMA. The plan says so at length and it is still true.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
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 "types.h"
|
|
#include "user.h"
|
|
#include "fcntl.h"
|
|
#include "fb.h"
|
|
|
|
#include "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;
|
|
}
|