Two things, both asked for. **The crash report no longer says which monitors it reached.** "The report is on the virtio-gpu display too" was a line about the crash screen printed on the crash screen. The report says what happened and where to read it again; that is not part of it. **And the second head now shows the F8 menu and the boot.** It used to be black until vgpud started several seconds into userspace -- through the splash, through the menu, through every boot message -- which from the outside is indistinguishable from a display that does not work. Three stages now cover the whole boot and hand over to each other without coordinating: *The loader.* LocateProtocol() answers with the first GOP and offers no way to ask for the rest, so the rest are found by handle and fed the primary's framebuffer as a Blt source at the points where a screen is finished: the menu, the resolution chooser, system information, the splash, each segment of the kernel load. Blt rather than a second framebuffer to draw into, because a virtio GOP does not have one -- the same fact that made a virtio-only machine reboot-loop two commits ago. *The kernel.* gfx remembers which rows were drawn, as one span rather than a list of rectangles, because console output is lines and a span is what lines make. display_mirror_console() copies that span across: a line of boot text is one cell-row transfer, not a screen. Primed once after pci_init(), because a display that has just appeared has a black scanout and the console it should be showing was drawn by the loader into a framebuffer it knows nothing about. *Then vgpud*, from the same console framebuffer, unchanged. **Two bugs found building it, both worth the retelling.** The mirror ran per character from inside vt_feed -- which is inside the console lock -- and vga_disp_acquire() announces its back buffer with a cprintf. A CPU taking a lock it already holds, with a call chain that said exactly that. It runs at the end of consolewrite() and cprintf() now, outside the lock, which is also the better batching: one transfer per write instead of one per character. And "is this the console's own screen" was asked of the pointer acquire() returned. vga_fb hands back a back buffer it copies onto the console's framebuffer afterwards, so on a one-screen machine the answer was "a different screen" -- and the console was mirrored onto itself through two megabytes of intermediate, allocated for the purpose. display_ops grew scanout_phys, which is a fact about the hardware rather than a policy a driver can get subtly wrong, and it is asked first: it has no side effects and acquire() has two. Verified with two heads under QEMU, screendumping the virtio head every 400 ms from power-on: splash, splash, splash, then the console, and it stays. The F8 menu appears on it; choosing 1024x768 there brings both heads up at 1024x768 byte-identical. A panic still reaches both. Both supported configurations: one boot each, no traps, no panics, socktest and memfdtest green. One gap remains and is documented rather than hidden: while the kernel's driver resets the device during probe, QEMU shows "Display output is not active" for a fraction of a second. A device being taken over has no scanout until the new driver sets one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
701 lines
22 KiB
C
701 lines
22 KiB
C
// The device model. See include/device.h for why it exists.
|
|
//
|
|
// A fixed table rather than a linked list: this is populated once during boot
|
|
// by whichever bus found each device, and read afterwards. Nothing is removed
|
|
// yet -- hot-unplug would need it, and when that arrives the in_use flag is
|
|
// already the hook for it.
|
|
//
|
|
// No lock. Every device_add() happens during single-threaded boot, before
|
|
// userspace exists, and every reader afterwards only walks. If a bus ever
|
|
// enumerates at runtime -- USB hotplug is the obvious one -- this needs a
|
|
// spinlock, and the reader in procfs needs to take it.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "device.h"
|
|
#include "display.h"
|
|
#include "netdev.h"
|
|
#include "theme.h"
|
|
#include "text.h"
|
|
#include "spinlock.h"
|
|
#include "gfx.h"
|
|
#include "pci.h"
|
|
#include "mmu.h"
|
|
#include "memlayout.h"
|
|
|
|
static struct device devices[NDEVICE];
|
|
static int ndevices;
|
|
|
|
static const char *bus_names[DEVBUS_NBUS] = {
|
|
[DEVBUS_PLATFORM] = "platform",
|
|
[DEVBUS_PCI] = "pci",
|
|
[DEVBUS_USB] = "usb",
|
|
};
|
|
|
|
static const char *class_names[DEVCLASS_NCLASS] = {
|
|
[DEVCLASS_OTHER] = "other",
|
|
[DEVCLASS_DISPLAY] = "display",
|
|
[DEVCLASS_INPUT] = "input",
|
|
[DEVCLASS_STORAGE] = "storage",
|
|
[DEVCLASS_NET] = "net",
|
|
[DEVCLASS_BUS] = "bus",
|
|
};
|
|
|
|
const char *
|
|
dev_bus_name(enum dev_bus b)
|
|
{
|
|
if (b < 0 || b >= DEVBUS_NBUS || !bus_names[b])
|
|
return "?";
|
|
return bus_names[b];
|
|
}
|
|
|
|
const char *
|
|
dev_class_name(enum dev_class c)
|
|
{
|
|
if (c < 0 || c >= DEVCLASS_NCLASS || !class_names[c])
|
|
return "?";
|
|
return class_names[c];
|
|
}
|
|
|
|
// How many devices already share this base name, so "vga" becomes vga0, vga1.
|
|
static int
|
|
count_base(const char *base)
|
|
{
|
|
int n = 0;
|
|
for (int i = 0; i < ndevices; i++) {
|
|
const char *a = devices[i].name, *b = base;
|
|
while (*b && *a == *b) { a++; b++; }
|
|
// Matched the whole base and the rest is the index we appended.
|
|
if (*b == '\0' && *a >= '0' && *a <= '9')
|
|
n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
struct device *
|
|
device_add(const char *base, enum dev_bus bus, enum dev_class class,
|
|
const char *driver)
|
|
{
|
|
if (ndevices >= NDEVICE)
|
|
return 0;
|
|
|
|
struct device *d = &devices[ndevices++];
|
|
d->bus = bus;
|
|
d->class = class;
|
|
d->driver = driver;
|
|
d->in_use = 1;
|
|
|
|
int idx = count_base(base);
|
|
int n = 0;
|
|
while (base[n] && n < DEV_NAMELEN - 3) {
|
|
d->name[n] = base[n];
|
|
n++;
|
|
}
|
|
// Single digit is enough for NDEVICE of 32 per base name, and a two-digit
|
|
// path that is never taken is a two-digit path that is never tested.
|
|
if (idx > 9)
|
|
idx = 9;
|
|
d->name[n++] = (char)('0' + idx);
|
|
d->name[n] = '\0';
|
|
|
|
return d;
|
|
}
|
|
|
|
void
|
|
device_set_desc(struct device *d, const char *s)
|
|
{
|
|
if (!d)
|
|
return;
|
|
int n = 0;
|
|
while (s[n] && n < (int)sizeof(d->desc) - 1) {
|
|
d->desc[n] = s[n];
|
|
n++;
|
|
}
|
|
d->desc[n] = '\0';
|
|
}
|
|
|
|
struct device *
|
|
device_at(int i)
|
|
{
|
|
if (i < 0 || i >= ndevices)
|
|
return 0;
|
|
return &devices[i];
|
|
}
|
|
|
|
int
|
|
device_count(void)
|
|
{
|
|
return ndevices;
|
|
}
|
|
|
|
struct device *
|
|
device_find_class(enum dev_class class)
|
|
{
|
|
for (int i = 0; i < ndevices; i++)
|
|
// Unclaimed devices are deliberately skipped. A display nobody drives is
|
|
// not a display you can draw on, and returning it would push the "is there
|
|
// a driver" check out to every caller.
|
|
if (devices[i].class == class && devices[i].driver)
|
|
return &devices[i];
|
|
return 0;
|
|
}
|
|
|
|
// --- the display class -----------------------------------------------------
|
|
//
|
|
// Thin wrappers over device_find_class() plus a null check on each op. They
|
|
// exist so that consumers are not each written around a vtable that may be
|
|
// absent -- a compositor asking to present on a machine with no display driver
|
|
// should be a no-op, not a null dereference in the middle of a frame.
|
|
|
|
struct device *
|
|
display_get(void)
|
|
{
|
|
return device_find_class(DEVCLASS_DISPLAY);
|
|
}
|
|
|
|
int
|
|
display_mode(struct display_mode *out)
|
|
{
|
|
struct device *d = display_get();
|
|
if (!d || !d->ops || !((struct display_ops *)d->ops)->mode)
|
|
return -1;
|
|
((struct display_ops *)d->ops)->mode(d, out);
|
|
return 0;
|
|
}
|
|
|
|
void *
|
|
display_acquire(uint32 *stride_out)
|
|
{
|
|
struct device *d = display_get();
|
|
if (!d || !d->ops || !((struct display_ops *)d->ops)->acquire)
|
|
return 0;
|
|
return ((struct display_ops *)d->ops)->acquire(d, stride_out);
|
|
}
|
|
|
|
void
|
|
display_present(int x, int y, int w, int h)
|
|
{
|
|
struct device *d = display_get();
|
|
if (!d || !d->ops || !((struct display_ops *)d->ops)->present)
|
|
return;
|
|
((struct display_ops *)d->ops)->present(d, x, y, w, h);
|
|
}
|
|
|
|
// Where this display's pixels actually end up. 0 when the driver does not say,
|
|
// which callers must read as "assume it is the console's" -- see the comment
|
|
// on scanout_phys in include/display.h.
|
|
uint64
|
|
display_scanout_phys(void)
|
|
{
|
|
struct device *d = display_get();
|
|
if (!d || !d->ops || !((struct display_ops *)d->ops)->scanout_phys)
|
|
return 0;
|
|
return ((struct display_ops *)d->ops)->scanout_phys(d);
|
|
}
|
|
|
|
void
|
|
display_wait_vblank(void)
|
|
{
|
|
struct device *d = display_get();
|
|
if (!d || !d->ops || !((struct display_ops *)d->ops)->wait_vblank)
|
|
return;
|
|
((struct display_ops *)d->ops)->wait_vblank(d);
|
|
}
|
|
|
|
// --- the crash screen, on every display the machine has ---------------------
|
|
//
|
|
// panic() paints the report into the console framebuffer, which is one screen
|
|
// on a machine that may have several. The others showed whatever they were
|
|
// showing when the machine died -- a shell prompt, most misleadingly -- and
|
|
// this is what walks them and puts the report there instead.
|
|
//
|
|
// Called from panic(), with interrupts off and everything the rules of that
|
|
// place imply: no allocation, no locks, no sleeping. It is deliberately the
|
|
// last thing the paint path does, so that a fault in here cannot cost the
|
|
// screen that was already drawn correctly.
|
|
//
|
|
// Two cases, and they are not the same:
|
|
//
|
|
// the kernel still drives it display_acquire() hands back a buffer; copy
|
|
// and present, the ordinary path
|
|
// userspace drives it the kernel let go of the device and the
|
|
// process holding it will not run again, so
|
|
// the driver it was taken from is asked to
|
|
// take it back for one frame
|
|
//
|
|
// The second is the one worth having. It is also the one that only works
|
|
// because the userspace driver said in advance where it was scanning out --
|
|
// see PCIIOC_PANICFB in include/pciuser.h.
|
|
|
|
// Copy the console framebuffer into a surface, with the same rules vgpud's
|
|
// mirror uses: the overlap, centred if the surface is larger, top-left if it
|
|
// is smaller. Both sides are XRGB8888; anything else is refused by whoever
|
|
// registered it.
|
|
// Copy rows [r0, r1] of the console framebuffer into a surface, with the same
|
|
// rules vgpud's mirror uses: the overlap, centred if the surface is larger,
|
|
// top-left if it is smaller. Both sides are XRGB8888; anything else is refused
|
|
// by whoever registered it. Returns the destination row span it wrote, so the
|
|
// caller can present exactly that.
|
|
static int
|
|
copy_console_rows(volatile uchar *dst, int dw, int dh, uint32 dstride,
|
|
int r0, int r1, int *out_y, int *out_h)
|
|
{
|
|
const volatile uchar *src = gfx_fb_ptr();
|
|
int sw = (int)gfx_width(), sh = (int)gfx_height();
|
|
uint32 sstride = gfx_fb_stride();
|
|
int cw = sw < dw ? sw : dw;
|
|
int ch = sh < dh ? sh : dh;
|
|
int x0 = (dw - cw) / 2, y0 = (dh - ch) / 2;
|
|
|
|
if (!src || gfx_fb_bpp() != 32 || cw <= 0 || ch <= 0)
|
|
return 0;
|
|
if (r0 < 0) r0 = 0;
|
|
if (r1 > ch - 1) r1 = ch - 1;
|
|
if (r1 < r0)
|
|
return 0;
|
|
|
|
for (int y = r0; y <= r1; y++) {
|
|
const volatile uint32 *s = (const volatile uint32 *)(src + (uint64)y * sstride);
|
|
volatile uint32 *d = (volatile uint32 *)(dst + (uint64)(y0 + y) * dstride);
|
|
for (int x = 0; x < cw; x++)
|
|
d[x0 + x] = s[x];
|
|
}
|
|
*out_y = y0 + r0;
|
|
*out_h = r1 - r0 + 1;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
panic_copy_console(volatile uchar *dst, int dw, int dh, uint32 dstride)
|
|
{
|
|
int y, h;
|
|
copy_console_rows(dst, dw, dh, dstride, 0, dh, &y, &h);
|
|
}
|
|
|
|
// --- the console, on a second screen ---------------------------------------
|
|
//
|
|
// A machine with two displays shows the console on one of them and whatever
|
|
// the other device happened to be left holding on the other -- which during
|
|
// boot is nothing at all: a black rectangle next to the machine starting up.
|
|
//
|
|
// So the rows the console draws are copied across as they are drawn. Cheap
|
|
// because gfx remembers which rows those were: a line of boot text is one
|
|
// cell-row transfer, not a screen.
|
|
//
|
|
// This is the same job vgpud does from userspace, and the two do not overlap.
|
|
// While the kernel drives the second GPU this runs; the moment vgpud claims
|
|
// it, display_acquire() returns nothing here and vgpud's mirror takes over
|
|
// from the same console framebuffer. The handover needs no coordination
|
|
// because neither side owns anything the other wants.
|
|
void
|
|
display_mirror_console(void)
|
|
{
|
|
// cprintf() from inside the display driver would come straight back here
|
|
// and present again, for ever.
|
|
static int inside;
|
|
// And two terminals on two CPUs would interleave into one command queue.
|
|
// The lock is held across the copy and the present, which is microseconds of
|
|
// polled virtio and the same thing the panic path does with everything else
|
|
// already stopped.
|
|
static struct spinlock lk;
|
|
static int lk_init;
|
|
// Which buffer was last brought up to date; see below.
|
|
static volatile uchar *last_buf;
|
|
struct display_mode m;
|
|
uint32 stride = 0;
|
|
volatile uchar *buf;
|
|
int r0, r1, y, h;
|
|
|
|
if (inside || !gfx_ready())
|
|
return;
|
|
|
|
// Is there a second screen at all? Asked first, and asked of the *scanout*,
|
|
// because both halves of that matter.
|
|
//
|
|
// Of the scanout, because the buffer acquire() returns is not the same
|
|
// thing: vga_fb hands back a back buffer it copies onto the console's own
|
|
// framebuffer afterwards, so comparing pointers said "a different screen"
|
|
// about the only screen there is -- and then mirrored the console onto
|
|
// itself through two megabytes of intermediate.
|
|
//
|
|
// First, because this question has no side effects and acquire() has two:
|
|
// it allocates a back buffer on first call, and it announces it with a
|
|
// cprintf -- which comes straight back here. On a one-screen machine both
|
|
// were pure waste, and the cprintf was a panic.
|
|
uint64 phys = display_scanout_phys();
|
|
if (phys == 0 || phys == gfx_fb_phys())
|
|
return;
|
|
|
|
if (!lk_init) {
|
|
initlock(&lk, "dispmirror");
|
|
lk_init = 1;
|
|
}
|
|
acquire(&lk);
|
|
// Set before the first call into the driver, not after: a driver may
|
|
// cprintf, cprintf comes back here, and the second entry would block on a
|
|
// lock its own call chain is holding.
|
|
inside = 1;
|
|
buf = display_acquire(&stride);
|
|
|
|
if (!buf || display_mode(&m) != 0 || m.bpp != 32) {
|
|
inside = 0;
|
|
release(&lk);
|
|
return;
|
|
}
|
|
|
|
// A buffer we have not written before is owed the whole screen, not the
|
|
// damage since last time.
|
|
//
|
|
// This is what a handover looks like from here: the loader draws the splash
|
|
// straight into the framebuffer, the kernel's display driver probes some
|
|
// milliseconds later with a black scanout of its own, and gfx has no damage
|
|
// to report because gfx did not draw any of it. Without this the second head
|
|
// went black at that moment and filled in a line at a time as output
|
|
// arrived. It happens once per driver, and the same rule covers the device
|
|
// coming back from a userspace driver that exited.
|
|
int have = gfx_damage_take(&r0, &r1);
|
|
if (buf != last_buf) {
|
|
last_buf = buf;
|
|
r0 = 0;
|
|
r1 = (int)m.height - 1;
|
|
have = 1;
|
|
}
|
|
if (!have) {
|
|
inside = 0;
|
|
release(&lk);
|
|
return;
|
|
}
|
|
|
|
if (copy_console_rows(buf, m.width, m.height, stride ? stride : m.stride,
|
|
r0, r1, &y, &h))
|
|
display_present(0, y, m.width, h);
|
|
inside = 0;
|
|
release(&lk);
|
|
}
|
|
|
|
void
|
|
display_panic_present(void)
|
|
{
|
|
if (!gfx_ready())
|
|
return;
|
|
|
|
// The kernel's own display, when it is not the console's. On a machine where
|
|
// the console is the VBE framebuffer and a virtio-gpu is the display class,
|
|
// these are two different screens and only one of them has the report.
|
|
{
|
|
struct display_mode m;
|
|
uint32 stride = 0;
|
|
volatile uchar *buf = display_acquire(&stride);
|
|
|
|
uint64 phys = display_scanout_phys();
|
|
|
|
if (buf && phys && phys != gfx_fb_phys() &&
|
|
display_mode(&m) == 0 && m.bpp == 32) {
|
|
panic_copy_console(buf, m.width, m.height, stride ? stride : m.stride);
|
|
display_present(0, 0, m.width, m.height);
|
|
}
|
|
}
|
|
|
|
// And every display the kernel handed to a process. The report is painted
|
|
// into memory that process registered, then its old kernel driver is asked
|
|
// to put it on screen -- which means resetting the device out from under a
|
|
// driver that is still nominally running. That is safe here and nowhere
|
|
// else: nothing is going to run again.
|
|
for (int i = 0; i < pci_count() && i < NPCI; i++) {
|
|
const struct pci_driver *drv = 0;
|
|
uint64 paddr = 0;
|
|
uint32 stride = 0;
|
|
int w = 0, h = 0;
|
|
|
|
if (!pciuser_panic_surface(i, &paddr, &w, &h, &stride, &drv))
|
|
continue;
|
|
// A packed surface, because that is what the device is about to be told it
|
|
// is. A padded one would shear, which is worse than not appearing.
|
|
if (stride != (uint32)w * 4)
|
|
continue;
|
|
|
|
panic_copy_console((volatile uchar *)P2V(paddr), w, h, stride);
|
|
drv->panic_present((struct pci_dev *)pci_get(i), paddr, w, h);
|
|
}
|
|
}
|
|
|
|
// Drive the whole display path once and report what happened: find the class,
|
|
// read the mode, acquire a back buffer, draw into RAM, present it.
|
|
//
|
|
// A self-test rather than a unit test because none of this can be checked
|
|
// without a display: the assertion is "did the right pixels reach the screen",
|
|
// and the only instrument for that is a screendump. Triggered with
|
|
// `echo d > /proc/sysrq` and photographed by tools/dispshot.ps1.
|
|
//
|
|
// The pattern is chosen to be wrong in visible ways. Four solid quadrants in
|
|
// theme colours catch a stride error (the bands would slant) and a bpp error
|
|
// (the colours would be wrong); the white frame one pixel inside the edge
|
|
// catches an off-by-one in the present rectangle; and the fact that it is
|
|
// drawn entirely into RAM before a single byte reaches the framebuffer is what
|
|
// double buffering means.
|
|
void
|
|
display_selftest(void)
|
|
{
|
|
struct display_mode m;
|
|
uint32 stride;
|
|
|
|
if (display_mode(&m) != 0) {
|
|
cprintf("display: no display bound\n");
|
|
return;
|
|
}
|
|
cprintf("display: %dx%d %dbpp stride %d\n", m.width, m.height, m.bpp, m.stride);
|
|
|
|
unsigned char *b = display_acquire(&stride);
|
|
if (!b) {
|
|
cprintf("display: no back buffer available\n");
|
|
return;
|
|
}
|
|
|
|
static const uint32 quad[4] = {
|
|
SCH_ACCENT, SCH_ACCENT_DIM, SCH_DIM, SCH_BG
|
|
};
|
|
for (int y = 0; y < m.height; y++) {
|
|
uint32 *row = (uint32 *)(b + (uint64)y * stride);
|
|
int top = y < m.height / 2;
|
|
for (int x = 0; x < m.width; x++) {
|
|
int left = x < m.width / 2;
|
|
row[x] = quad[(top ? 0 : 2) + (left ? 0 : 1)];
|
|
}
|
|
}
|
|
for (int x = 1; x < m.width - 1; x++) {
|
|
*(uint32 *)(b + 1 * (uint64)stride + x * 4) = SCH_FG;
|
|
*(uint32 *)(b + (uint64)(m.height - 2) * stride + x * 4) = SCH_FG;
|
|
}
|
|
for (int y = 1; y < m.height - 1; y++) {
|
|
*(uint32 *)(b + (uint64)y * stride + 1 * 4) = SCH_FG;
|
|
*(uint32 *)(b + (uint64)y * stride + (m.width - 2) * 4) = SCH_FG;
|
|
}
|
|
|
|
// Between frames, so the blit does not land halfway down a scan-out.
|
|
display_wait_vblank();
|
|
display_present(0, 0, m.width, m.height);
|
|
cprintf("display: presented a full frame\n");
|
|
}
|
|
|
|
// Draw a text specimen through the display class. `echo t > /proc/sysrq`.
|
|
//
|
|
// It exercises the three things a character-cell console cannot do, so a
|
|
// screenshot says whether the layer works rather than whether it compiled:
|
|
// glyphs on a common baseline at arbitrary x (not multiples of eight), text
|
|
// blended over an existing background rather than drawn into cleared cells, and
|
|
// codepoints above 255.
|
|
void
|
|
text_selftest(void)
|
|
{
|
|
struct display_mode m;
|
|
uint32 stride;
|
|
|
|
if (display_mode(&m) != 0) {
|
|
cprintf("text: no display\n");
|
|
return;
|
|
}
|
|
unsigned char *b = display_acquire(&stride);
|
|
if (!b) {
|
|
cprintf("text: no back buffer\n");
|
|
return;
|
|
}
|
|
|
|
struct surface s = { b, stride, m.width, m.height };
|
|
struct font *f = font_builtin();
|
|
|
|
// A background with structure in it, so blending is visible as blending
|
|
// rather than as drawing onto a flat colour.
|
|
for (int y = 0; y < m.height; y++) {
|
|
uint32 *row = (uint32 *)(b + (uint64)y * stride);
|
|
for (int x = 0; x < m.width; x++)
|
|
row[x] = ((x / 32 + y / 32) & 1) ? SCH_BG : SCH_ACCENT_DIM;
|
|
}
|
|
|
|
int base = f->ops->baseline(f);
|
|
int line = f->ops->height(f) + 6;
|
|
int y = 80;
|
|
|
|
text_draw(&s, f, 60, y + base, "SchOS text rendering", SCH_FG);
|
|
y += line * 2;
|
|
|
|
// Deliberately not on a multiple of eight. On the old console this string
|
|
// could only start at x = 56 or x = 64.
|
|
text_draw(&s, f, 61, y + base, "positioned at x=61, off the cell grid", SCH_DIM);
|
|
y += line;
|
|
text_draw(&s, f, 63, y + base, "and here at x=63", SCH_DIM);
|
|
y += line * 2;
|
|
|
|
// Above U+00FF, which a 256-entry table cannot index at all.
|
|
text_draw(&s, f, 60, y + base,
|
|
"utf-8: \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x90 "
|
|
"\xe2\x96\x88\xe2\x96\x93\xe2\x96\x92\xe2\x96\x91 "
|
|
"\xc2\xb0 \xc2\xb1 \xe2\x86\x92 \xe2\x80\xa2", SCH_ACCENT);
|
|
y += line * 2;
|
|
|
|
// Measure, then centre on it -- the thing you cannot do without metrics.
|
|
const char *msg = "measured and centred";
|
|
int w = text_measure(f, msg);
|
|
text_draw(&s, f, (m.width - w) / 2, y + base, msg, SCH_FG);
|
|
|
|
display_present(0, 0, m.width, m.height);
|
|
cprintf("text: drew a specimen, \"%s\" measures %d px\n", msg, w);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The network class. See include/netdev.h.
|
|
//
|
|
// Plural where the display class is singular: a machine with two NICs is
|
|
// ordinary, and "the network" is not a thing a stack can ask for the way a
|
|
// compositor asks for "the display". So this walks, and the caller decides
|
|
// what to do with each interface it finds.
|
|
|
|
struct device *
|
|
netdev_at(int i)
|
|
{
|
|
int seen = 0;
|
|
|
|
for (int k = 0; k < device_count(); k++) {
|
|
struct device *d = device_at(k);
|
|
if (!d || d->class != DEVCLASS_NET || !d->driver || !d->ops)
|
|
continue;
|
|
if (seen++ == i)
|
|
return d;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
netdev_count(void)
|
|
{
|
|
int n = 0;
|
|
|
|
while (netdev_at(n))
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
int
|
|
netdev_transmit(struct device *d, const void *frame, int len)
|
|
{
|
|
if (!d || !d->ops || !((struct netdev_ops *)d->ops)->transmit)
|
|
return -1;
|
|
return ((struct netdev_ops *)d->ops)->transmit(d, frame, len);
|
|
}
|
|
|
|
int
|
|
netdev_receive(struct device *d, void *out, int cap)
|
|
{
|
|
if (!d || !d->ops || !((struct netdev_ops *)d->ops)->receive)
|
|
return 0;
|
|
return ((struct netdev_ops *)d->ops)->receive(d, out, cap);
|
|
}
|
|
|
|
const unsigned char *
|
|
netdev_mac(struct device *d)
|
|
{
|
|
if (!d || !d->ops || !((struct netdev_ops *)d->ops)->mac)
|
|
return 0;
|
|
return ((struct netdev_ops *)d->ops)->mac(d);
|
|
}
|
|
|
|
int
|
|
netdev_link_up(struct device *d)
|
|
{
|
|
// A driver that cannot tell is treated as up. Reporting "down" for a link
|
|
// whose state is simply unknown would stop the stack on hardware that works.
|
|
if (!d || !d->ops || !((struct netdev_ops *)d->ops)->link_up)
|
|
return 1;
|
|
return ((struct netdev_ops *)d->ops)->link_up(d);
|
|
}
|
|
|
|
static void
|
|
put_be16(uint8 *p, uint16 v)
|
|
{
|
|
p[0] = (uint8)(v >> 8);
|
|
p[1] = (uint8)v;
|
|
}
|
|
|
|
// The ARP probe. Written for the e1000 when it was the only driver and there
|
|
// was no stack to test it with; it belongs here because it is the first thing
|
|
// anyone will want to run against the *next* driver, and none of it is
|
|
// specific to a chip.
|
|
//
|
|
// Under QEMU's user-mode networking the guest is 10.0.2.15 and the gateway is
|
|
// 10.0.2.2. SLIRP is silent until spoken to, which is why this has to transmit
|
|
// before it can expect to receive anything.
|
|
void
|
|
netdev_selftest(void)
|
|
{
|
|
struct device *d = netdev_at(0);
|
|
uint8 frame[42];
|
|
uint8 reply[2048];
|
|
|
|
if (!d) {
|
|
cprintf("netdev: no interface\n");
|
|
return;
|
|
}
|
|
|
|
const unsigned char *mac = netdev_mac(d);
|
|
if (!mac) {
|
|
cprintf("netdev: %s has no station address\n", d->name);
|
|
return;
|
|
}
|
|
|
|
static const uint8 me[4] = { 10, 0, 2, 15 }; // QEMU SLIRP guest address
|
|
static const uint8 them[4] = { 10, 0, 2, 2 }; // and its gateway
|
|
|
|
memset(frame, 0, sizeof(frame));
|
|
for (int i = 0; i < 6; i++)
|
|
frame[i] = 0xFF; // broadcast
|
|
for (int i = 0; i < 6; i++)
|
|
frame[6 + i] = mac[i];
|
|
put_be16(&frame[12], 0x0806); // ARP
|
|
|
|
put_be16(&frame[14], 1); // Ethernet
|
|
put_be16(&frame[16], 0x0800); // IPv4
|
|
frame[18] = 6; // hardware address length
|
|
frame[19] = 4; // protocol address length
|
|
put_be16(&frame[20], 1); // request
|
|
for (int i = 0; i < 6; i++)
|
|
frame[22 + i] = mac[i]; // sender hardware address
|
|
for (int i = 0; i < 4; i++)
|
|
frame[28 + i] = me[i]; // sender protocol address
|
|
// target hardware address stays zero -- that is what is being asked
|
|
for (int i = 0; i < 4; i++)
|
|
frame[38 + i] = them[i];
|
|
|
|
cprintf("netdev: %s asking who has %d.%d.%d.%d\n",
|
|
d->name, them[0], them[1], them[2], them[3]);
|
|
if (netdev_transmit(d, frame, sizeof(frame)) != 0) {
|
|
cprintf("netdev: %s transmit ring full\n", d->name);
|
|
return;
|
|
}
|
|
|
|
// Bounded poll. This runs from a sysrq write and there is nothing to sleep
|
|
// on that would be woken by a raw frame, so spinning briefly is honest.
|
|
//
|
|
// It will also swallow whatever else is on the wire, including the stack's
|
|
// own traffic if it is running -- which is why a non-ARP frame is reported
|
|
// rather than treated as a failure.
|
|
for (int spin = 0; spin < 20000000; spin++) {
|
|
int n = netdev_receive(d, reply, sizeof(reply));
|
|
if (n <= 0)
|
|
continue;
|
|
|
|
cprintf("netdev: %d bytes from %x:%x:%x:%x:%x:%x type %x\n",
|
|
n, reply[6], reply[7], reply[8], reply[9], reply[10], reply[11],
|
|
((uint32)reply[12] << 8) | reply[13]);
|
|
|
|
if (n >= 42 && reply[12] == 0x08 && reply[13] == 0x06 &&
|
|
reply[20] == 0 && reply[21] == 2) {
|
|
cprintf("netdev: ARP reply -- %d.%d.%d.%d is at %x:%x:%x:%x:%x:%x\n",
|
|
reply[28], reply[29], reply[30], reply[31],
|
|
reply[22], reply[23], reply[24], reply[25],
|
|
reply[26], reply[27]);
|
|
cprintf("netdev: %s self-test passed\n", d->name);
|
|
return;
|
|
}
|
|
}
|
|
cprintf("netdev: %s no reply\n", d->name);
|
|
}
|