// 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, (int)m.bpp }; 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); }