// 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 "core/types.h" #include "uapi/user.h" #include "uapi/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; }