Removes the "-unix" element and with it any trademark exposure to The
Open Group.
The split follows the ecosystem layout: SchOS is the system, and the
kernel has its own name. Kernel-identity strings are the only places
that take SchIFT -- uname(2) sysname, /proc/version, and the two licence
sentences that say "kernel" outright. That is what every Unix does here:
`uname -s` on Linux reports the kernel, not the distribution. Everything
a user reads as the product -- panic screen, boot menu, error prefixes,
docs, LICENSE, NOTICE -- is SchOS.
schunix.img is schos.img, the logo assets and the musl TLS patch are
renamed, and __SCHUNIX__ is __SCHOS__ (the define and the patch that
consumes it moved together).
Two things the rename broke, both found by the bootcheck suite:
- shmtest wrote "SCHUNIX-MMAP-OK" with a hardcoded length of 15 and
compared it back by hand-counted offsets (m[3]=='U', m[14]=='K').
The shorter marker left the write running two bytes past the literal
and the compare reading past the data, failing a file-backed mmap
that was working correctly. Now written and compared through one
constant with sizeof.
- bootcheck asserted a 31-byte file after appending to
"hello-from-schunix"; the shorter string makes it 29.
Both are the same mistake: a string literal whose length is written out
somewhere else. Full suite green -- base, Tty, Ps, Sock, Fb, Usb, Evdev.
308 lines
8.8 KiB
C
308 lines
8.8 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 "theme.h"
|
|
#include "text.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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
}
|