SchOS/ldls/kpi/work.c
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

142 lines
4.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// A driver's periodic work. See ldls/kpi/kpi.h for the provenance rule.
//
// e1000 is the reason this exists, and the way it failed is worth keeping.
// The driver probed, opened, accepted frames from the stack, mapped them and
// wrote the transmit tail register -- and the wire stayed silent. Reading the
// chip's own registers said why:
//
// TCTL=0x010000f8 PSP, collision threshold, retry-on-late-collision
// -- and bit 1, the enable, clear
// RDT=0 no receive descriptors offered to the device
//
// Neither is set by `ndo_open`. e1000 enables its transmitter in
// `e1000_watchdog`, once it has seen the link come up, and refills the receive
// ring from the same place. The watchdog is a `delayed_work`, and LDLS was
// dropping every one on the floor: `queue_delayed_work_on` resolved to a stub
// that returned zero, which is the correct return value and does nothing.
//
// So a hosted driver needs somewhere for deferred work to actually run. There
// is no thread to run it on, which is the same constraint that made the NAPI
// poll a loop -- and the same answer: keep the function, and call it from the
// loop that was going to spin anyway.
//
// **A work item here runs on the main loop, not concurrently.** That is why
// there are no locks, and it is also the limitation: a driver whose work item
// blocks stops everything, including the transmit path it is probably waiting
// for.
#include "types.h"
#include "user.h"
#include "posix.h"
#include "kpi.h"
#define KPI_NWORK 16
static struct {
void *work; // struct work_struct *, our identity
unsigned long due; // in jiffies
int armed;
} wq[KPI_NWORK];
static void
rdfn(void *work, void (**fn)(void *))
{
unsigned char *p = (unsigned char *)work + kpi_l.work_func;
int i;
for (i = 0; i < 8; i++) ((unsigned char *)fn)[i] = p[i];
}
static int
slot_for(void *work)
{
int i, free = -1;
for (i = 0; i < KPI_NWORK; i++) {
if (wq[i].armed && wq[i].work == work)
return i;
if (!wq[i].armed && free < 0)
free = i;
}
return free;
}
static int
arm(void *work, unsigned long delay)
{
int i;
if (kpi_l.work_func == 0) {
kpi_log("kpi: deferred work with no work_struct.func offset\n");
return 0;
}
i = slot_for(work);
if (i < 0) {
kpi_log("kpi: too many deferred work items\n");
return 0;
}
// Re-arming an item that is already queued moves its deadline, which is what
// queue_delayed_work does *not* do -- it leaves the first deadline standing.
// The difference only shows up as work running sooner than a driver asked,
// and running a watchdog early is harmless in a way that running it late is
// not.
wq[i].work = work;
wq[i].due = kpi_jiffies_now() + delay;
wq[i].armed = 1;
return 1;
}
// queue_work_on(cpu, wq, work)
int kpi_queue_work_on(int cpu, void *q, void *work)
{ (void)cpu; (void)q; return arm(work, 0); }
// queue_delayed_work_on(cpu, wq, dwork, delay)
//
// `struct delayed_work` begins with its `work_struct`, so the pointer is
// already the right one -- which is the only reason this does not need a
// second BTF offset.
int
kpi_queue_delayed_work_on(int cpu, void *q, void *dwork, unsigned long delay)
{ (void)cpu; (void)q; return arm(dwork, delay); }
int
kpi_cancel_work_sync(void *work)
{
int i = slot_for(work);
if (i >= 0 && wq[i].armed && wq[i].work == work) { wq[i].armed = 0; return 1; }
return 0;
}
int kpi_cancel_delayed_work_sync(void *dwork) { return kpi_cancel_work_sync(dwork); }
int kpi_cancel_delayed_work(void *dwork) { return kpi_cancel_work_sync(dwork); }
int kpi_flush_work(void *work) { (void)work; return 0; }
void kpi_flush_workqueue(void *q) { (void)q; }
// Run whatever is due. Returns how many ran, so the caller's loop can tell
// whether anything happened.
//
// The slot is disarmed *before* the call, because the overwhelming majority of
// periodic work in a driver ends by queueing itself again -- and disarming
// after would throw that away and stop the clock.
int
kpi_work_run(void)
{
unsigned long now = kpi_jiffies_now();
int i, n = 0;
for (i = 0; i < KPI_NWORK; i++) {
void (*fn)(void *) = 0;
void *w;
if (!wq[i].armed || (long)(now - wq[i].due) < 0)
continue;
w = wq[i].work;
wq[i].armed = 0;
rdfn(w, &fn);
if (!fn)
continue;
fn(w);
n++;
}
return n;
}