Compare commits

...

4 Commits

Author SHA1 Message Date
marv7000 32f1ff25f7 Make clock.c atomic 2026-10-18 21:15:35 +02:00
marv7000 8aad7c5023 Don't print the TID in kmsg 2026-10-18 21:14:00 +02:00
marv7000 8c100af7e7 Implement uacpi_kernel_unmap 2026-10-18 21:13:29 +02:00
marv7000 b6ffd2c8c6 Add basic tools 2026-10-18 18:27:34 +02:00
16 changed files with 677 additions and 32 deletions
+24 -28
View File
@@ -1,54 +1,50 @@
#define pr_fmt(fmt) "clock: " fmt #define pr_fmt(fmt) "clock: " fmt
#include <zinnia/atomic.h>
#include <zinnia/clock.h> #include <zinnia/clock.h>
#include <zinnia/compiler.h> #include <zinnia/compiler.h>
#include <zinnia/print.h> #include <zinnia/print.h>
#include <zinnia/spin.h>
#include <arch/cpu.h> #include <arch/cpu.h>
#include <stdint.h> #include <stdint.h>
static struct clock* active_clock = nullptr; static _Atomic(struct clock*) active_clock;
static uint64_t counter_base = 0; static _Atomic(int64_t) realtime_offset_ns;
static struct spinlock lock = {};
static uint64_t clock_get_elapsed_ns_locked() { static uint64_t clock_read_elapsed(struct clock* clock) {
if (__unlikely(!active_clock)) if (__unlikely(!clock))
return 0; return 0;
return counter_base + active_clock->get_elapsed_ns(active_clock); return clock->base + clock->get_elapsed_ns(clock);
} }
bool clock_available() { bool clock_available() {
spin_lock(&lock); return atomic_load(&active_clock, ATOMIC_ACQUIRE) != nullptr;
bool available = active_clock != nullptr;
spin_unlock(&lock);
return available;
} }
uint64_t clock_get_elapsed_ns() { uint64_t clock_get_elapsed_ns() {
spin_lock(&lock); return clock_read_elapsed(atomic_load(&active_clock, ATOMIC_ACQUIRE));
uint64_t elapsed = clock_get_elapsed_ns_locked(); }
spin_unlock(&lock);
return elapsed; uint64_t clock_get_realtime_ns() {
int64_t ns = (int64_t)clock_get_elapsed_ns() + atomic_load(&realtime_offset_ns, ATOMIC_RELAXED);
return ns < 0 ? 0 : ns;
}
void clock_set_realtime_ns(uint64_t ns) {
atomic_store(&realtime_offset_ns, (int64_t)ns - (int64_t)clock_get_elapsed_ns(), ATOMIC_RELAXED);
} }
bool clock_switch(struct clock* clock) { bool clock_switch(struct clock* clock) {
spin_lock(&lock); struct clock* old = atomic_load(&active_clock, ATOMIC_ACQUIRE);
if (active_clock) { do {
if (clock->priority <= active_clock->priority) { if (old && clock->priority <= old->priority)
spin_unlock(&lock);
return false; return false;
}
}
uint64_t elapsed = clock_get_elapsed_ns_locked(); uint64_t elapsed = clock_read_elapsed(old);
clock->reset(clock); clock->reset(clock);
clock->base = elapsed;
counter_base = elapsed; } while (!atomic_compare_exchange(&active_clock, &old, clock, ATOMIC_RELEASE, ATOMIC_ACQUIRE));
active_clock = clock;
spin_unlock(&lock);
log("Switching to clock source \"%s\"\n", clock->name); log("Switching to clock source \"%s\"\n", clock->name);
+1 -2
View File
@@ -442,10 +442,9 @@ static void log_emit(log_fn_t callback, void* ctx, const struct log_record* rec,
log_cb_printf( log_cb_printf(
callback, callback,
ctx, ctx,
"[%5lu.%06lu] [%5lu] %s", "[%5lu.%06lu] %s",
rec->time_ns / 1'000'000'000, rec->time_ns / 1'000'000'000,
rec->time_ns / 1'000 % 1'000'000, rec->time_ns / 1'000 % 1'000'000,
(uint64_t)rec->tid,
log_level_colors[rec->level] log_level_colors[rec->level]
); );
callback(ctx, msg, rec->len); callback(ctx, msg, rec->len);
+9 -2
View File
@@ -1,4 +1,5 @@
#include <zinnia/acpi.h> #include <zinnia/acpi.h>
#include <zinnia/alloc.h>
#include <zinnia/console.h> #include <zinnia/console.h>
#include <zinnia/init.h> #include <zinnia/init.h>
#include <zinnia/mmio.h> #include <zinnia/mmio.h>
@@ -28,14 +29,20 @@ void* uacpi_kernel_map(uacpi_phys_addr addr, uacpi_size len) {
return nullptr; return nullptr;
for (size_t i = 0; i < aligned_len; i += arch_mem_page_size()) { for (size_t i = 0; i < aligned_len; i += arch_mem_page_size()) {
pmap_map(&kernel_vma.pmap, vaddr + i, aligned_addr + i, PROT_READ | PROT_WRITE, CACHE_ANY); pmap_map(&kernel_vma.pmap, vaddr + i, aligned_addr + i, PTE_READ | PTE_WRITE, CACHE_ANY);
} }
return (void*)(vaddr + difference); return (void*)(vaddr + difference);
} }
void uacpi_kernel_unmap(void* addr, uacpi_size len) { void uacpi_kernel_unmap(void* addr, uacpi_size len) {
// TODO uintptr_t aligned_addr = ALIGN_DOWN((uintptr_t)addr, arch_mem_page_size());
uintptr_t difference = (uintptr_t)addr - aligned_addr;
size_t aligned_len = ALIGN_UP(len + difference, arch_mem_page_size());
for (size_t i = 0; i < aligned_len; i += arch_mem_page_size())
pmap_unmap(&kernel_vma.pmap, aligned_addr + i);
vm_free(aligned_addr, aligned_len);
} }
static enum log_level acpi_log_level(uacpi_log_level lvl) { static enum log_level acpi_log_level(uacpi_log_level lvl) {
+5
View File
@@ -5,6 +5,7 @@
struct clock { struct clock {
const char* name; const char* name;
uint8_t priority; uint8_t priority;
uint64_t base;
void (*reset)(struct clock* c); void (*reset)(struct clock* c);
uint64_t (*get_elapsed_ns)(struct clock* c); uint64_t (*get_elapsed_ns)(struct clock* c);
@@ -19,5 +20,9 @@ bool clock_available();
// Gets the current timestamp in nanoseconds since the last reset. // Gets the current timestamp in nanoseconds since the last reset.
uint64_t clock_get_elapsed_ns(); uint64_t clock_get_elapsed_ns();
uint64_t clock_get_realtime_ns();
void clock_set_realtime_ns(uint64_t ns);
// Spin for `ns` nanoseconds. // Spin for `ns` nanoseconds.
void clock_spin_ns(uint64_t ns); void clock_spin_ns(uint64_t ns);
+5
View File
@@ -26,6 +26,11 @@ c_compiler = meson.get_compiler('c')
# We require a compiler that supports the GNU C dialect. # We require a compiler that supports the GNU C dialect.
assert(c_compiler.has_define('__GNUC__')) assert(c_compiler.has_define('__GNUC__'))
if get_option('tools_only')
subdir('tools')
subdir_done()
endif
add_project_arguments( add_project_arguments(
'-Wno-unused', '-Wno-unused',
'-Wno-unused-parameter', '-Wno-unused-parameter',
+7
View File
@@ -5,6 +5,13 @@ option(
description: 'Only install headers', description: 'Only install headers',
) )
option(
'tools_only',
type: 'boolean',
value: false,
description: 'Only build userspace tools',
)
option( option(
'config_path', 'config_path',
type: 'string', type: 'string',
+19
View File
@@ -0,0 +1,19 @@
[binaries]
c = 'clang'
cpp = 'clang++'
ar = 'llvm-ar'
strip = 'llvm-strip'
objcopy = 'llvm-objcopy'
ld = 'ld.lld'
[built-in options]
c_args = ['-target', 'aarch64-unknown-none']
cpp_args = ['-target', 'aarch64-unknown-none']
c_link_args = ['--target=aarch64-unknown-linux-gnu', '-fuse-ld=lld']
cpp_link_args = ['--target=aarch64-unknown-linux-gnu', '-fuse-ld=lld']
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'
+19
View File
@@ -0,0 +1,19 @@
[binaries]
c = 'clang'
cpp = 'clang++'
ar = 'llvm-ar'
strip = 'llvm-strip'
objcopy = 'llvm-objcopy'
ld = 'ld.lld'
[built-in options]
c_args = ['-target', 'riscv64-unknown-none']
cpp_args = ['-target', 'riscv64-unknown-none']
c_link_args = ['--target=riscv64-unknown-linux-gnu', '-fuse-ld=lld']
cpp_link_args = ['--target=riscv64-unknown-linux-gnu', '-fuse-ld=lld']
[host_machine]
system = 'linux'
cpu_family = 'riscv64'
cpu = 'riscv64'
endian = 'little'
+19
View File
@@ -0,0 +1,19 @@
[binaries]
c = 'clang'
cpp = 'clang++'
ar = 'llvm-ar'
strip = 'llvm-strip'
objcopy = 'llvm-objcopy'
ld = 'ld.lld'
[built-in options]
c_args = ['-target', 'x86_64-unknown-none']
cpp_args = ['-target', 'x86_64-unknown-none']
c_link_args = ['--target=x86_64-unknown-linux-gnu', '-fuse-ld=lld']
cpp_link_args = ['--target=x86_64-unknown-linux-gnu', '-fuse-ld=lld']
[host_machine]
system = 'linux'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
+42
View File
@@ -0,0 +1,42 @@
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "usage: %s <tty> <command> [args...]\n", argv[0]);
return 2;
}
const char* tty = argv[1];
if (setsid() < 0 && getpid() != getsid(0)) {
perror("setsid");
return 1;
}
int fd = open(tty, O_RDWR);
if (fd < 0) {
perror(tty);
return 1;
}
if (ioctl(fd, TIOCSCTTY, 0) != 0)
perror("TIOCSCTTY");
if (dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0) {
perror("dup2");
return 1;
}
if (fd > STDERR_FILENO)
close(fd);
execvp(argv[2], &argv[2]);
perror(argv[2]);
return 1;
}
+180
View File
@@ -0,0 +1,180 @@
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#define DEVCTL_PATH "/dev/devctl"
#define SOCKET_PATH "/var/run/devd.pipe"
#define MAX_CLIENTS 32
#define DEVCTL_BUF 4096
#define LINE_BUF 1024
static int clients[MAX_CLIENTS];
static int nclients = 0;
// Accumulates a partial line read from /dev/devctl across reads.
static char line[LINE_BUF];
static size_t line_len = 0;
static void add_client(int fd) {
if (nclients >= MAX_CLIENTS) {
close(fd);
return;
}
clients[nclients++] = fd;
}
static void remove_client(int idx) {
close(clients[idx]);
clients[idx] = clients[--nclients];
}
// Broadcast a single notification line (including its trailing newline).
static void broadcast(const char* buf, size_t len) {
for (int i = 0; i < nclients;) {
ssize_t off = 0;
int dead = 0;
while (off < (ssize_t)len) {
ssize_t n = write(clients[i], buf + off, len - off);
if (n < 0) {
if (errno == EINTR)
continue;
dead = 1;
break;
}
off += n;
}
if (dead)
remove_client(i);
else
i++;
}
}
// Feed raw bytes from /dev/devctl.
static void feed(const char* data, size_t len) {
for (size_t i = 0; i < len; i++) {
char c = data[i];
if (line_len < sizeof(line) - 1)
line[line_len++] = c;
if (c == '\n') {
broadcast(line, line_len);
line_len = 0;
}
}
// Drop an overlong line that never terminated, to avoid wedging.
if (line_len >= sizeof(line) - 1)
line_len = 0;
}
static int open_devctl(void) {
int fd = open(DEVCTL_PATH, O_RDONLY | O_CLOEXEC);
if (fd < 0)
perror("devd: open " DEVCTL_PATH);
return fd;
}
static int make_listener(void) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
perror("devd: socket");
return -1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
unlink(SOCKET_PATH);
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("devd: bind " SOCKET_PATH);
close(fd);
return -1;
}
chmod(SOCKET_PATH, 0666);
if (listen(fd, 8) < 0) {
perror("devd: listen");
close(fd);
return -1;
}
return fd;
}
int main(void) {
// A client going away must not kill us.
signal(SIGPIPE, SIG_IGN);
int devctl = open_devctl();
if (devctl < 0)
return 1;
int listener = make_listener();
if (listener < 0)
return 1;
printf("devd: listening on %s, forwarding from %s\n", SOCKET_PATH, DEVCTL_PATH);
fflush(stdout);
for (;;) {
struct pollfd pfds[2 + MAX_CLIENTS];
pfds[0].fd = devctl;
pfds[0].events = POLLIN;
pfds[1].fd = listener;
pfds[1].events = POLLIN;
for (int i = 0; i < nclients; i++) {
pfds[2 + i].fd = clients[i];
pfds[2 + i].events = 0; // Only watch for hangup.
}
int n = poll(pfds, 2 + nclients, -1);
if (n < 0) {
if (errno == EINTR)
continue;
perror("devd: poll");
break;
}
// New notifications from the kernel.
if (pfds[0].revents & POLLIN) {
char buf[DEVCTL_BUF];
ssize_t r = read(devctl, buf, sizeof(buf));
if (r > 0) {
feed(buf, (size_t)r);
} else if (r == 0 || (r < 0 && errno != EINTR && errno != EAGAIN)) {
fprintf(stderr, "devd: %s closed, exiting\n", DEVCTL_PATH);
break;
}
}
// New client connections.
if (pfds[1].revents & POLLIN) {
int c = accept(listener, NULL, NULL);
if (c >= 0) {
fcntl(c, F_SETFD, FD_CLOEXEC);
add_client(c);
}
}
// Reap clients that hung up.
for (int i = nclients - 1; i >= 0; i--) {
if (pfds[2 + i].revents & (POLLHUP | POLLERR | POLLNVAL))
remove_client(i);
}
}
close(devctl);
close(listener);
unlink(SOCKET_PATH);
return 1;
}
+21
View File
@@ -0,0 +1,21 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/module.h>
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <module-path> [cmdline]\n", argv[0]);
return 2;
}
const char* path = argv[1];
const char* cmdline = argc >= 3 ? argv[2] : NULL;
if (insertmod(path, cmdline) != 0) {
perror("insertmod");
return 1;
}
return 0;
}
+283
View File
@@ -0,0 +1,283 @@
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
constexpr size_t LINE_MAX_LEN = 8192;
static const char* const level_names[] = {
"emerg",
"alert",
"crit",
"err",
"warn",
"notice",
"info",
"debug",
};
#define COLOR_RESET "\033[0m"
#define COLOR_TIME "\033[32m"
#define COLOR_SUBSYS "\033[33m"
static const char* const level_colors[] = {
"\033[7;31m",
"\033[7;31m",
"\033[1;31m",
"\033[31m",
"\033[1m",
"",
"",
"",
};
struct options {
bool follow;
bool raw;
bool decode;
bool color;
unsigned int level_mask;
};
static int hex_value(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static size_t unescape(const char* in, size_t len, char* out) {
size_t n = 0;
for (size_t i = 0; i < len; i++) {
if (in[i] == '\\' && i + 3 < len && in[i + 1] == 'x') {
const int hi = hex_value(in[i + 2]);
const int lo = hex_value(in[i + 3]);
if (hi >= 0 && lo >= 0) {
out[n++] = (char)(hi << 4 | lo);
i += 3;
continue;
}
}
out[n++] = in[i];
}
return n;
}
static const char* find_caller(char* header) {
char* save;
for (char* field = strtok_r(header, ",", &save); field; field = strtok_r(nullptr, ",", &save)) {
if (!strncmp(field, "caller=", 7))
return field + 7;
}
return nullptr;
}
static size_t subsystem_len(const char* text, size_t len) {
for (size_t i = 0; i + 1 < len; i++) {
if (text[i] == ':' && (text[i + 1] == ' ' || text[i + 1] == '\t'))
return i + 1;
}
return 0;
}
static void print_record(const struct options* opts, char* record, size_t len) {
if (opts->raw) {
printf("%.*s\n", (int)len, record);
return;
}
char* msg = memchr(record, ';', len);
if (!msg)
return;
*msg++ = '\0';
unsigned int prio;
uint64_t seq;
uint64_t usec;
if (sscanf(record, "%u,%" SCNu64 ",%" SCNu64, &prio, &seq, &usec) != 3)
return;
const unsigned int level = prio & 7;
if (!(opts->level_mask & (1u << level)))
return;
const char* caller = find_caller(record);
static char text[LINE_MAX_LEN];
const size_t text_len = unescape(msg, len - (size_t)(msg - record), text);
if (opts->decode)
printf("%-6s: ", level_names[level]);
if (opts->color)
printf(COLOR_TIME);
printf("[%5" PRIu64 ".%06" PRIu64 "]", usec / 1'000'000, usec % 1'000'000);
if (opts->color)
printf(COLOR_RESET);
putchar(' ');
if (caller)
printf("[%6s] ", caller);
const char* text_pos = text;
size_t remaining = text_len;
const size_t subsys = subsystem_len(text, text_len);
if (subsys) {
if (opts->color)
printf(COLOR_SUBSYS "%.*s" COLOR_RESET, (int)subsys, text);
else
printf("%.*s", (int)subsys, text);
text_pos += subsys;
remaining -= subsys;
}
const char* color = opts->color ? level_colors[level] : "";
if (*color)
printf("%s%.*s" COLOR_RESET "\n", color, (int)remaining, text_pos);
else
printf("%.*s\n", (int)remaining, text_pos);
}
static int parse_levels(const char* list, unsigned int* mask) {
char* copy = strdup(list);
if (!copy)
return -1;
*mask = 0;
char* save;
for (char* tok = strtok_r(copy, ",", &save); tok; tok = strtok_r(nullptr, ",", &save)) {
bool found = false;
for (unsigned int i = 0; i < 8; i++) {
if (!strcmp(tok, level_names[i])) {
*mask |= 1u << i;
found = true;
break;
}
}
if (!found) {
fprintf(stderr, "kmsg: unknown level '%s'\n", tok);
free(copy);
return -1;
}
}
free(copy);
return 0;
}
static void usage(const char* prog) {
fprintf(
stderr,
"usage: %s [-w] [-r] [-x] [-l level[,level...]] [--color=auto|always|never]\n"
" -w, --follow wait for new messages\n"
" -r, --raw print raw /dev/kmsg records\n"
" -x, --decode show the log level of each message\n"
" -l, --level only show the given levels (emerg,alert,crit,err,warn,notice,info,debug)\n"
" -L, --color colorize output (auto, always, never)\n",
prog
);
}
int main(int argc, char** argv) {
struct options opts = {
.level_mask = 0xFF,
};
int color_mode = -1;
static const struct option long_opts[] = {
{"follow", no_argument, nullptr, 'w'},
{"raw", no_argument, nullptr, 'r'},
{"decode", no_argument, nullptr, 'x'},
{"level", required_argument, nullptr, 'l'},
{"color", optional_argument, nullptr, 'L'},
{"help", no_argument, nullptr, 'h'},
{},
};
int c;
while ((c = getopt_long(argc, argv, "wrxl:L::h", long_opts, nullptr)) != -1) {
switch (c) {
case 'w':
opts.follow = true;
break;
case 'r':
opts.raw = true;
break;
case 'x':
opts.decode = true;
break;
case 'l':
if (parse_levels(optarg, &opts.level_mask))
return 1;
break;
case 'L':
if (!optarg || !strcmp(optarg, "always"))
color_mode = 1;
else if (!strcmp(optarg, "never"))
color_mode = 0;
else if (!strcmp(optarg, "auto"))
color_mode = -1;
else {
usage(argv[0]);
return 1;
}
break;
case 'h':
usage(argv[0]);
return 0;
default:
usage(argv[0]);
return 1;
}
}
opts.color = color_mode < 0 ? isatty(STDOUT_FILENO) : color_mode;
const int fd = open("/dev/kmsg", O_RDONLY | (opts.follow ? 0 : O_NONBLOCK));
if (fd < 0) {
perror("kmsg: /dev/kmsg");
return 1;
}
static char record[LINE_MAX_LEN];
while (true) {
const ssize_t n = read(fd, record, sizeof(record) - 1);
if (n < 0) {
if (errno == EINTR)
continue;
if (errno == EPIPE)
continue;
if (errno == EAGAIN)
break;
perror("kmsg: read");
close(fd);
return 1;
}
if (n == 0)
break;
record[n] = '\0';
for (char* line = record; line < record + n;) {
char* end = memchr(line, '\n', (size_t)(record + n - line));
if (!end)
end = record + n;
*end = '\0';
if (*line != ' ')
print_record(&opts, line, (size_t)(end - line));
line = end + 1;
}
if (opts.follow)
fflush(stdout);
}
close(fd);
return 0;
}
+6
View File
@@ -0,0 +1,6 @@
executable('console', 'console.c', install: true)
executable('devd', 'devd.c', install: true)
executable('insertmod', 'insertmod.c', install: true)
executable('kmsg', 'kmsg.c', install: true)
executable('mount', 'mount.c', install: true)
executable('umount', 'umount.c', install: true)
+21
View File
@@ -0,0 +1,21 @@
#include <stdio.h>
#include <string.h>
#include <sys/mount.h>
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "usage: %s <fstype> <target> [source]\n", argv[0]);
return 2;
}
const char* fstype = argv[1];
const char* target = argv[2];
const char* source = argc >= 4 ? argv[3] : NULL;
if (mount(fstype, target, 0, (void*)source) != 0) {
perror("mount");
return 1;
}
return 0;
}
+16
View File
@@ -0,0 +1,16 @@
#include <stdio.h>
#include <sys/mount.h>
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <target>\n", argv[0]);
return 2;
}
if (unmount(argv[1], 0) != 0) {
perror("umount");
return 1;
}
return 0;
}