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>
142 lines
4.4 KiB
C
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;
|
|
}
|