mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-13 11:37:55 +08:00
This commit is contained in:
1 parent
b71e04d39a
commit
c4ac5442bc
11 files changed
+857
-32
No files matched your search
@@ -0,0 +1,59 @@
|
|||||||
|
#
|
||||||
|
# LuCI status & settings app for the wwand modem daemon: the modem status page,
|
||||||
|
# the Modems editor, live settings, and the shared resources (wwand.bands /
|
||||||
|
# wwand.modemopts / wwand.simlist) reused by the proto handler.
|
||||||
|
#
|
||||||
|
# Sources live in https://github.com/ddimension/luci-app-wwand — bump
|
||||||
|
# PKG_SOURCE_VERSION (and PKG_SOURCE_DATE) to pick up new source commits.
|
||||||
|
#
|
||||||
|
# NOTE: built from a git PKG_SOURCE, so it does NOT use luci.mk — luci.mk's
|
||||||
|
# htdocs/root install is gated on those dirs living next to the Makefile
|
||||||
|
# (in-tree), which yields an EMPTY package for a git-fetched source. We install
|
||||||
|
# explicitly from $(PKG_BUILD_DIR), like the wwand package in this feed.
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=luci-app-wwand
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_SOURCE_PROTO:=git
|
||||||
|
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
|
||||||
|
PKG_SOURCE_VERSION:=1c03a023715367e8ce36f9c50ac6ebdaa1e49459
|
||||||
|
PKG_SOURCE_DATE:=2026-08-13
|
||||||
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
|
PKG_LICENSE:=GPL-2.0-only
|
||||||
|
PKG_MAINTAINER:=
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Package/luci-app-wwand
|
||||||
|
SECTION:=luci
|
||||||
|
CATEGORY:=LuCI
|
||||||
|
SUBMENU:=3. Applications
|
||||||
|
TITLE:=wwand cellular modem status & settings
|
||||||
|
DEPENDS:=+luci-base +wwand
|
||||||
|
PKGARCH:=all
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/luci-app-wwand/description
|
||||||
|
LuCI status and configuration app for the wwand cellular modem daemon.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/luci-app-wwand/install
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/resources
|
||||||
|
$(CP) $(PKG_BUILD_DIR)/htdocs/luci-static/resources/* \
|
||||||
|
$(1)/www/luci-static/resources/
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/root/usr/share/luci/menu.d/*.json \
|
||||||
|
$(1)/usr/share/luci/menu.d/
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/root/usr/share/rpcd/acl.d/*.json \
|
||||||
|
$(1)/usr/share/rpcd/acl.d/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,luci-app-wwand))
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#
|
||||||
|
# LuCI network-manager protocol handler for the wwand modem daemon.
|
||||||
|
# Registers only the 'wwand' protocol UI (protocol/wwand.js). It deliberately
|
||||||
|
# does NOT ship a 'qmi' alias: `proto qmi` stays with luci-proto-qmi/uqmi (no
|
||||||
|
# file collision, good-citizen coexistence), and interfaces are moved to
|
||||||
|
# `proto wwand` via the migration list in the LuCI modem page.
|
||||||
|
#
|
||||||
|
# Sources live in https://github.com/ddimension/luci-proto-wwand — bump
|
||||||
|
# PKG_SOURCE_VERSION (and PKG_SOURCE_DATE) to pick up new source commits.
|
||||||
|
#
|
||||||
|
# NOTE: this is built from a git PKG_SOURCE, so it does NOT use luci.mk. luci.mk
|
||||||
|
# gates its htdocs/root install on those directories living NEXT TO the Makefile
|
||||||
|
# (in-tree); for a git-fetched source they only exist under $(PKG_BUILD_DIR), so
|
||||||
|
# luci.mk produces an EMPTY package. We install explicitly from $(PKG_BUILD_DIR),
|
||||||
|
# exactly like the wwand package in this feed.
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=luci-proto-wwand
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_SOURCE_PROTO:=git
|
||||||
|
PKG_SOURCE_URL:=https://github.com/ddimension/luci-proto-wwand.git
|
||||||
|
PKG_SOURCE_VERSION:=7d9ac0fe15257ac6469d9e2c0228506789200229
|
||||||
|
PKG_SOURCE_DATE:=2026-08-11
|
||||||
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
|
PKG_LICENSE:=GPL-2.0-only
|
||||||
|
PKG_MAINTAINER:=
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Package/luci-proto-wwand
|
||||||
|
SECTION:=luci
|
||||||
|
CATEGORY:=LuCI
|
||||||
|
SUBMENU:=4. Protocols
|
||||||
|
TITLE:=Support for QMI/5G cellular modems (wwand)
|
||||||
|
# luci-app-wwand provides the shared wwand.bands resource (band/frequency
|
||||||
|
# helpers) used by this handler; depending on it keeps a single source of truth.
|
||||||
|
DEPENDS:=+luci-base +wwand +luci-app-wwand
|
||||||
|
PKGARCH:=all
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/luci-proto-wwand/description
|
||||||
|
LuCI protocol handler for the wwand cellular modem daemon: the network-native
|
||||||
|
'wwand' proto plus the legacy 'qmi' alias.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/luci-proto-wwand/install
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/resources/protocol
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/htdocs/luci-static/resources/protocol/*.js \
|
||||||
|
$(1)/www/luci-static/resources/protocol/
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/root/usr/share/rpcd/acl.d/*.json \
|
||||||
|
$(1)/usr/share/rpcd/acl.d/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,luci-proto-wwand))
|
||||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
|||||||
FOOTSTRAP_VERSION?=
|
FOOTSTRAP_VERSION?=
|
||||||
ifneq ($(FOOTSTRAP_VERSION),)
|
ifneq ($(FOOTSTRAP_VERSION),)
|
||||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||||
PKG_RELEASE:=24
|
PKG_RELEASE:=25
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LUCI_TITLE:=Footstrap Theme
|
LUCI_TITLE:=Footstrap Theme
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ const GEOM_DFLT = { contentMin: 500, sidebarW: 224, railW: 68, contentPad: 56 };
|
|||||||
let _geom = null, _geomDensity = null, _geomWarned = false;
|
let _geom = null, _geomDensity = null, _geomWarned = false;
|
||||||
function shellGeometry() {
|
function shellGeometry() {
|
||||||
const density = document.documentElement.getAttribute('data-density') || '';
|
const density = document.documentElement.getAttribute('data-density') || '';
|
||||||
|
/* the gutter is re-asked even on the memo hit: it moves with the WIDTH, not with the density */
|
||||||
if (_geom && _geomDensity === density) return _geom;
|
if (_geom && _geomDensity === density) return _geom;
|
||||||
_geomDensity = density;
|
_geomDensity = density;
|
||||||
const px = (name, dflt) => resolveLen(name, dflt);
|
const px = (name, dflt) => resolveLen(name, dflt);
|
||||||
@@ -161,7 +162,9 @@ function shellGeometry() {
|
|||||||
contentMin: px('--fs-content-min', GEOM_DFLT.contentMin),
|
contentMin: px('--fs-content-min', GEOM_DFLT.contentMin),
|
||||||
sidebarW: px('--fs-sidebar-w', GEOM_DFLT.sidebarW),
|
sidebarW: px('--fs-sidebar-w', GEOM_DFLT.sidebarW),
|
||||||
railW: px('--fs-rail-w', GEOM_DFLT.railW),
|
railW: px('--fs-rail-w', GEOM_DFLT.railW),
|
||||||
/* the token is ONE side's padding; the column loses it twice */
|
/* the token is ONE side's padding; the column loses it twice. Kept as `tokenPad` because it
|
||||||
|
* is only the FALLBACK — the gutter that counts is measured, see measureShell() */
|
||||||
|
tokenPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2,
|
||||||
contentPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2
|
contentPad: px('--fs-content-pad', GEOM_DFLT.contentPad / 2) * 2
|
||||||
};
|
};
|
||||||
/* Plausibility, and it costs one comparison: the rail IS the sidebar collapsed, so
|
/* Plausibility, and it costs one comparison: the rail IS the sidebar collapsed, so
|
||||||
@@ -183,21 +186,67 @@ function shellGeometry() {
|
|||||||
+ 'renamed, or a foreign sheet is reaching the measurement probe.');
|
+ 'renamed, or a foreign sheet is reaching the measurement probe.');
|
||||||
}
|
}
|
||||||
_geom = sane ? g : Object.assign({}, GEOM_DFLT);
|
_geom = sane ? g : Object.assign({}, GEOM_DFLT);
|
||||||
|
if (_geom.tokenPad === undefined) _geom.tokenPad = _geom.contentPad;
|
||||||
|
/* the token is only the fallback; the gutter that counts is the one measureShell() read */
|
||||||
|
if (_shellPad != null) _geom.contentPad = _shellPad;
|
||||||
return _geom;
|
return _geom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* THE GUTTER IS MEASURED WHERE IT IS APPLIED, not read off the token that usually supplies it, and
|
||||||
|
* SO IS THE WINDOW — but both are read HERE, from a fitter, and nowhere else.
|
||||||
|
*
|
||||||
|
* `--fs-content-pad` is what `.fs-content` uses at desktop widths, and `theme/20-shell.css` gives
|
||||||
|
* the same element `padding: var(--fs-space-4)` below 767px: the token says 28px a side while the
|
||||||
|
* column's gutter is 16px there. That is a 24px error in a model this file exists to keep honest,
|
||||||
|
* and `live-audit` found it on every page at 320, 390 and 568 — the same trap the alert-message rule
|
||||||
|
* under that media query carries a paragraph about, repeated in the JS. The breakpoint itself may
|
||||||
|
* NOT come here: a width literal copied into JS is exactly what these reads exist to avoid, and a
|
||||||
|
* media query has no `data-*` to key on either. Asking the element what it actually got is the only
|
||||||
|
* form that cannot drift.
|
||||||
|
*
|
||||||
|
* WHY IT IS A SEPARATE FUNCTION AND WHY ONLY A FITTER CALLS IT. `clientWidth` is a LAYOUT read and
|
||||||
|
* `getComputedStyle` resolves style; `contentWidth()` below is called mid-scroll, by the one pass in
|
||||||
|
* this theme that must answer without reading either (fs-select's, for a table the poll brought in
|
||||||
|
* under the reader's thumb). So the numbers are taken when a fitter runs — which is on every resize
|
||||||
|
* and every content mutation, and never during a flick, because `fitChrome()` defers exactly like
|
||||||
|
* every other measuring pass — and everything after that reads what was stored. Nothing they
|
||||||
|
* describe can change without a resize, and a resize schedules a fit.
|
||||||
|
*
|
||||||
|
* Unlike the token probe above, a hostile app's declaration is not a threat here: `.fs-content`
|
||||||
|
* carries no chrome mark, so an app CAN restyle it, and if it did then that padding IS the column's
|
||||||
|
* real gutter. What the probe must not report is a foreign answer for OUR layout; this reports the
|
||||||
|
* layout as it stands. Before any fitter has run — the login page has no `.fs-content` at all — the
|
||||||
|
* token stands in. */
|
||||||
|
let _shellOuter = 0, _shellPad = null;
|
||||||
|
function measureShell() {
|
||||||
|
_shellOuter = document.documentElement.clientWidth;
|
||||||
|
const host = document.querySelector('.fs-content');
|
||||||
|
const cs = host ? getComputedStyle(host) : null;
|
||||||
|
const v = cs ? parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight) : NaN;
|
||||||
|
if (Number.isFinite(v) && v >= 0) {
|
||||||
|
_shellPad = v;
|
||||||
|
if (_geom) _geom.contentPad = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function columnWidth(g, state) {
|
||||||
|
/* narrow OR top: in both the chrome is above the content, not beside it */
|
||||||
|
const cut = (state.narrow || state.top) ? 0 : (state.rail ? g.railW : g.sidebarW);
|
||||||
|
return Math.max(0, state.outerW - cut - g.contentPad);
|
||||||
|
}
|
||||||
|
|
||||||
function fitShell() {
|
function fitShell() {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
|
/* the one place the window and the gutter are read — see measureShell(). It runs in BOTH
|
||||||
|
* branches: the bar layout decides nothing here, but `contentWidth()` still answers in it. */
|
||||||
|
measureShell();
|
||||||
if (prefs.currentLayout() === 'top') { /* no sidebar, no cut, nothing to decide */
|
if (prefs.currentLayout() === 'top') { /* no sidebar, no cut, nothing to decide */
|
||||||
root.removeAttribute('data-narrow');
|
root.removeAttribute('data-narrow');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const g = shellGeometry();
|
const g = shellGeometry();
|
||||||
const cut = prefs.currentRail() ? g.railW : g.sidebarW;
|
/* asked UNCOLLAPSED — this is the measurement that decides `data-narrow`, so it may not read it */
|
||||||
/* clientWidth, not innerWidth: the column the content actually gets excludes a classic
|
const content = columnWidth(g, { outerW: _shellOuter, rail: prefs.currentRail() });
|
||||||
* scrollbar, and innerWidth includes it — 15-17px of phantom room on Linux/Windows, i.e. the
|
|
||||||
* 500px floor really fired at ~484. */
|
|
||||||
const content = document.documentElement.clientWidth - cut - g.contentPad;
|
|
||||||
/* toggleAttribute, NOT setAttribute: a same-value setAttribute still QUEUES a mutation record
|
/* toggleAttribute, NOT setAttribute: a same-value setAttribute still QUEUES a mutation record
|
||||||
* (measured in Chromium: 5 identical setAttribute('data-narrow','') -> 5 records; toggleAttribute
|
* (measured in Chromium: 5 identical setAttribute('data-narrow','') -> 5 records; toggleAttribute
|
||||||
* on an already-present attribute -> 0). fitShell runs from fitChrome, which fs-fit calls on every
|
* on an already-present attribute -> 0). fitShell runs from fitChrome, which fs-fit calls on every
|
||||||
@@ -476,15 +525,30 @@ return baseclass.extend({
|
|||||||
* a known amount of the window and the shell adds a known padding, and all three are already
|
* a known amount of the window and the shell adds a known padding, and all three are already
|
||||||
* memoised against the density attribute for `fitShell()`. It is exported because a pass that
|
* memoised against the density attribute for `fitShell()`. It is exported because a pass that
|
||||||
* must answer mid-scroll — fs-select's, for a table the poll has just brought in — otherwise has
|
* must answer mid-scroll — fs-select's, for a table the poll has just brought in — otherwise has
|
||||||
* only `window.innerWidth`, which in the sidebar layout is the wrong number by exactly the
|
* only the window's own width, which in the sidebar layout is the wrong number by exactly the
|
||||||
* sidebar: at an 800px window the column is 520px, so the cheap judgement said "plenty of room"
|
* sidebar: at an 800px window the column is 520px, so the cheap judgement said "plenty of room"
|
||||||
* for a table that then overflowed and was clipped. */
|
* for a table that then overflowed and was clipped.
|
||||||
|
*
|
||||||
|
* The arithmetic itself is columnWidth()'s, shared with fitShell — see there. All this adds is
|
||||||
|
* the page's current state, which fitShell is in the middle of deciding and this one reads. */
|
||||||
contentWidth() {
|
contentWidth() {
|
||||||
const g = shellGeometry();
|
/* NO LAYOUT READ, and that is the whole point of this export: the window's width and the
|
||||||
const narrow = document.documentElement.hasAttribute('data-narrow');
|
* column's gutter are whatever the last fitter measured (measureShell), and the three
|
||||||
/* in the bar layout the chrome is above the content, not beside it */
|
* attributes below are style, not layout. The bootstrap read is for a caller that arrives
|
||||||
const cut = narrow ? 0 : (prefs.currentRail() ? g.railW : g.sidebarW);
|
* before any fitter has run, which cannot happen mid-scroll. */
|
||||||
return Math.max(0, window.innerWidth - cut - (g.contentPad * 2));
|
if (!_shellOuter) measureShell();
|
||||||
|
const root = document.documentElement;
|
||||||
|
return columnWidth(shellGeometry(), {
|
||||||
|
outerW: _shellOuter,
|
||||||
|
narrow: root.hasAttribute('data-narrow'),
|
||||||
|
top: prefs.isTopLayout(),
|
||||||
|
rail: prefs.currentRail()
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* exported for the unit suite (tests/chrome-geometry.test.mjs): the cut is pure arithmetic over
|
||||||
|
* four measured numbers, and driving it directly is the only way to hold every combination of
|
||||||
|
* layout, rail and width without a browser */
|
||||||
|
columnWidth,
|
||||||
wireRail
|
wireRail
|
||||||
});
|
});
|
||||||
@@ -37,25 +37,38 @@ const _viewIntervals = (window.__fsViewIntervals || (window.__fsViewIntervals =
|
|||||||
if (window.__fsIntervalsHooked) return;
|
if (window.__fsIntervalsHooked) return;
|
||||||
window.__fsIntervalsHooked = true;
|
window.__fsIntervalsHooked = true;
|
||||||
const _si = window.setInterval, _ci = window.clearInterval;
|
const _si = window.setInterval, _ci = window.clearInterval;
|
||||||
|
/* THE ID THE CALLER GOT IS THE ID IT KEEPS, whatever the pause below does underneath. The map is
|
||||||
|
* keyed by that first id and the entry carries `live`, the id the platform has armed RIGHT NOW
|
||||||
|
* (null while paused) — so a view holding its handle can still stop its own poller after a trip
|
||||||
|
* through a hidden tab, which is exactly what re-arming under a fresh id took away from it.
|
||||||
|
* The arguments are kept for the same reason: a `setInterval` id carries none of them back. */
|
||||||
window.setInterval = function (fn, ms) {
|
window.setInterval = function (fn, ms) {
|
||||||
const id = _si.apply(window, arguments);
|
const id = _si.apply(window, arguments);
|
||||||
/* the arguments are kept, not just the id: a paused timer has to be re-armed with what it
|
_viewIntervals.set(id, { fn, ms, rest: Array.prototype.slice.call(arguments, 2), live: id });
|
||||||
* was armed with, and a `setInterval` id carries none of that back */
|
|
||||||
_viewIntervals.set(id, { fn, ms, rest: Array.prototype.slice.call(arguments, 2) });
|
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
window.clearInterval = function (id) {
|
window.clearInterval = function (id) {
|
||||||
|
const spec = _viewIntervals.get(id);
|
||||||
_viewIntervals.delete(id);
|
_viewIntervals.delete(id);
|
||||||
|
/* A PAUSED TIMER IS ALREADY DISARMED, and its number is the platform's to hand out again —
|
||||||
|
* clearing it here would stop whatever timer holds it now. Untracked ids fall through
|
||||||
|
* unchanged: the hook must stay a pass-through for everything it did not arm. */
|
||||||
|
if (spec) return (spec.live == null) ? undefined : _ci.call(window, spec.live);
|
||||||
return _ci.apply(window, arguments);
|
return _ci.apply(window, arguments);
|
||||||
};
|
};
|
||||||
/* A HIDDEN TAB MUST NOT KEEP CALLING THE ROUTER. `wireVisibility()` below stops LuCI's own poll
|
/* A HIDDEN TAB MUST NOT KEEP CALLING THE ROUTER. `wireVisibility()` below stops LuCI's own poll
|
||||||
* when the tab goes away, which is most of the traffic — but a view is free to run a plain
|
* when the tab goes away, which is most of the traffic — but a view is free to run a plain
|
||||||
* `setInterval` of its own (luci-app-podkop's log tailer does), and those kept hammering ubus in
|
* `setInterval` of its own (luci-app-podkop's log tailer does), and those kept hammering ubus in
|
||||||
* a background tab for as long as it stayed open. The registry that navigation already uses to
|
* a background tab for as long as it stayed open. The registry that navigation already uses to
|
||||||
* clear them is enough to pause them too: cleared on hide, re-armed on show with the same
|
* clear them is enough to pause them too: disarmed on hide, re-armed on show with the same
|
||||||
* callback and period, so a view that was polling every 3 s is polling every 3 s again and one
|
* callback and period, so a view that was polling every 3 s is polling every 3 s again and one
|
||||||
* that was cleared meanwhile stays cleared. */
|
* that was cleared meanwhile stays cleared.
|
||||||
let _paused = [];
|
*
|
||||||
|
* A PAUSED TIMER STAYS IN THE REGISTRY, armed on nothing. It was carried in a private list
|
||||||
|
* beside it, and a list is not what the navigation sweep reads: hide the tab while a navigation
|
||||||
|
* is in flight (a click, then straight to another tab), and coming back re-armed the timers of
|
||||||
|
* the page that navigation had already replaced — the sweep had run while they were somewhere
|
||||||
|
* it could not see. In the map, `clearViewIntervals()` takes them like any other. */
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
/* LuCI'S OWN TICK IS NOT OURS TO PAUSE, and taking it made the page poll FASTER the
|
/* LuCI'S OWN TICK IS NOT OURS TO PAUSE, and taking it made the page poll FASTER the
|
||||||
@@ -75,18 +88,22 @@ const _viewIntervals = (window.__fsViewIntervals || (window.__fsViewIntervals =
|
|||||||
* costs a doubling that never stops. Same judgement as clearViewIntervals(). */
|
* costs a doubling that never stops. Same judgement as clearViewIntervals(). */
|
||||||
const keep = pollTickId();
|
const keep = pollTickId();
|
||||||
if (keep === false) return;
|
if (keep === false) return;
|
||||||
_paused = [];
|
|
||||||
for (const [ id, spec ] of _viewIntervals) {
|
for (const [ id, spec ] of _viewIntervals) {
|
||||||
if (id === keep) continue;
|
if (id === keep || spec.live == null) continue;
|
||||||
_paused.push(spec);
|
_ci.call(window, spec.live);
|
||||||
_ci.call(window, id);
|
spec.live = null;
|
||||||
_viewIntervals.delete(id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const back = _paused;
|
for (const spec of _viewIntervals.values()) {
|
||||||
_paused = [];
|
if (spec.live != null) continue;
|
||||||
for (const spec of back) window.setInterval(spec.fn, spec.ms, ...spec.rest);
|
/* `_si`, not the hook: this timer is already in the registry under the id its
|
||||||
|
* caller is holding, and re-registering it would key a second entry to a number
|
||||||
|
* nobody has. The fresh id lives in `spec.live` alone and is never a KEY, so it
|
||||||
|
* cannot collide with an entry — and the platform hands ids out in sequence, so a
|
||||||
|
* re-arm cannot be given a number some earlier caller is still holding either. */
|
||||||
|
spec.live = _si.call(window, spec.fn, spec.ms, ...spec.rest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
@@ -306,6 +323,22 @@ function markExpired() {
|
|||||||
* ended, and the very next click sends the user to a login form. */
|
* ended, and the very next click sends the user to a login form. */
|
||||||
console.warn('footstrap: the LuCI session is gone — every navigation from here is a full load.');
|
console.warn('footstrap: the LuCI session is gone — every navigation from here is a full load.');
|
||||||
}
|
}
|
||||||
|
/* AND THE VERDICT IS NOT A LATCH. It was, and the shape was wrong for a signal read off somebody
|
||||||
|
* else's reply: an interceptor sees `msg` only once the transport succeeded and the body parsed
|
||||||
|
* (rpc.js rejects before that), so a missing frame is not a network flap — but it IS a captive
|
||||||
|
* portal's page, a proxy's error body, one truncated reply. Any of those took the client router off
|
||||||
|
* for the rest of the document while the session was alive throughout, explained by one console
|
||||||
|
* line nobody reads until later.
|
||||||
|
*
|
||||||
|
* A clean `session.access` is the same call the failing one was, so it is evidence in the other
|
||||||
|
* direction and it is taken as such. If the session really has ended no clean one arrives, because
|
||||||
|
* every ubus call carries the same dead sid — the router stays off exactly as long as it should. */
|
||||||
|
function markAlive() {
|
||||||
|
if (!_expired) return;
|
||||||
|
_expired = false;
|
||||||
|
console.warn('footstrap: the LuCI session answers again — client navigation is back on.');
|
||||||
|
}
|
||||||
|
function sessionExpired() { return _expired; }
|
||||||
function watchSession() {
|
function watchSession() {
|
||||||
if (_sessionWired) return;
|
if (_sessionWired) return;
|
||||||
_sessionWired = true;
|
_sessionWired = true;
|
||||||
@@ -325,9 +358,22 @@ function watchSession() {
|
|||||||
rpc.addInterceptor((msg, r) => {
|
rpc.addInterceptor((msg, r) => {
|
||||||
try {
|
try {
|
||||||
if (!r || r.object !== 'session' || r.method !== 'access') return;
|
if (!r || r.object !== 'session' || r.method !== 'access') return;
|
||||||
if (!msg || msg.jsonrpc !== '2.0' ||
|
if (!msg || msg.jsonrpc !== '2.0') return;
|
||||||
(msg.error && msg.error.code && msg.error.message))
|
/* an `error` carrying both a code and a message is what handleCallReply() rejects
|
||||||
markExpired();
|
* on, and a rejected session probe is the signal. A frame that is not JSON-RPC 2.0
|
||||||
|
* is rejected there too, but it says nothing about the SESSION. */
|
||||||
|
if (msg.error && msg.error.code && msg.error.message) { markExpired(); return; }
|
||||||
|
/* AND ONLY `access: true` SAYS THE SESSION IS THERE — measured on the stands, because
|
||||||
|
* this is the one place where guessing costs the whole fix. A dead sid does not make
|
||||||
|
* this call fail: `session.access` answers `[0, {access:false}]` with HTTP 200 and no
|
||||||
|
* error frame at all (the `-32002` arrives on the ORDINARY call, which is what makes
|
||||||
|
* luci-base fire this probe in the first place). So "the reply parsed" would have
|
||||||
|
* been read as "the session is back", and the verdict a 403 had just reached would
|
||||||
|
* be cleared by the very probe that confirms it. `access:false` stays out of BOTH
|
||||||
|
* answers: an ACL denial for a restricted user looks exactly the same, which is why
|
||||||
|
* it may not expire a session either. */
|
||||||
|
if (Array.isArray(msg.result) && msg.result[1] && msg.result[1].access === true)
|
||||||
|
markAlive();
|
||||||
}
|
}
|
||||||
catch (e) { /* ditto */ }
|
catch (e) { /* ditto */ }
|
||||||
});
|
});
|
||||||
@@ -1503,6 +1549,11 @@ return baseclass.extend({
|
|||||||
/* exported for the unit suite (tests/router-contract.test.mjs), which drives it against a
|
/* exported for the unit suite (tests/router-contract.test.mjs), which drives it against a
|
||||||
* hand-broken `L` — the one way to see the OFF branch without a router that ships one */
|
* hand-broken `L` — the one way to see the OFF branch without a router that ships one */
|
||||||
contractBreaks,
|
contractBreaks,
|
||||||
|
/* likewise: tests/interval-pause.test.mjs drives the navigation sweep around a visibilitychange,
|
||||||
|
* and tests/session-expiry.test.mjs reads the verdict the interceptors reached. navigate() is
|
||||||
|
* the real caller of the first and `_expired` gates the second — nothing else may call either. */
|
||||||
|
clearViewIntervals,
|
||||||
|
sessionExpired,
|
||||||
/* fs-search warms the pages this admin actually uses (its recents) and the arrow-key-highlighted
|
/* fs-search warms the pages this admin actually uses (its recents) and the arrow-key-highlighted
|
||||||
* result, both of which the pointer/focus triggers above cannot see. The edge points that way
|
* result, both of which the pointer/focus triggers above cannot see. The edge points that way
|
||||||
* round — search → router — because the router must keep no dependency on the palette. */
|
* round — search → router — because the router must keep no dependency on the palette. */
|
||||||
|
|||||||
@@ -651,6 +651,34 @@ function fitTables() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AND THE TABLE ITSELF HAS TO BE MEASURABLE, WHICH ON ITS FIRST PASS IT IS NOT — because
|
||||||
|
* THIS FILE'S OWN GATE is what hides it. `theme/30-tables.css` holds a `.fs-dt` out of the
|
||||||
|
* layout until something marks it `.fs-fitted`, so the very first pass over a fresh table
|
||||||
|
* reads `scrollWidth: 0` — a `display: none` box has no content width — and 0 overflows
|
||||||
|
* nothing. The pass therefore concluded "it fits", wrote `.fs-fitted`, and CACHED that
|
||||||
|
* answer against the slot; the table then appeared at its natural width, 777px inside a
|
||||||
|
* 712px column, columns cut off by `.fs-main { overflow-x: clip }` with no scrollbar and
|
||||||
|
* nothing to say so.
|
||||||
|
*
|
||||||
|
* It usually corrected itself, which is what made it a report rather than a gate failure: a
|
||||||
|
* second pass ~60 ms later measured the now-visible table and broke its widest column. But
|
||||||
|
* that pass only exists if something mutates `#view` again, and Status → Processes renders
|
||||||
|
* once and then stands still. Measured on the stand at 768px, entering the page directly:
|
||||||
|
* 2 of 8 arrivals on 0.13.1 and 3 of 8 here left the table past the column, and one
|
||||||
|
* mutation of any kind fixed it instantly.
|
||||||
|
*
|
||||||
|
* So a zero measurement is not an answer to anything: lift the gate so the next frame can
|
||||||
|
* see the table, ask for that frame, and write NOTHING to the slot. The schedule is asked
|
||||||
|
* for once — on the pass that lifts the gate — so a table hidden for somebody else's reason
|
||||||
|
* (a closed section, a tab pane) cannot turn this into a frame loop. */
|
||||||
|
if (t.scrollWidth === 0) {
|
||||||
|
if (!t.classList.contains('fs-fitted')) {
|
||||||
|
t.classList.add('fs-fitted');
|
||||||
|
fit.schedule();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* the answer this slot already has, for a table of the same shape in the same room — UNLESS the
|
/* the answer this slot already has, for a table of the same shape in the same room — UNLESS the
|
||||||
* table has since outgrown it. Room and column count are not the whole input: a poll tick can
|
* table has since outgrown it. Room and column count are not the whole input: a poll tick can
|
||||||
* put longer values into the same columns, and then a table that fitted a second ago does not.
|
* put longer values into the same columns, and then a table that fitted a second ago does not.
|
||||||
|
|||||||
@@ -9,7 +9,13 @@
|
|||||||
-#}
|
-#}
|
||||||
{% if (dispatcher.lookup("admin/logout")): %}
|
{% if (dispatcher.lookup("admin/logout")): %}
|
||||||
<a class="fs-logout" href="{{ dispatcher.build_url('admin/logout') }}" title="{{ _('Log out') }}" aria-label="{{ _('Log out') }}">
|
<a class="fs-logout" href="{{ dispatcher.build_url('admin/logout') }}" title="{{ _('Log out') }}" aria-label="{{ _('Log out') }}">
|
||||||
{% include('themes/footstrap/partials/icon', { body: '<path d="M14 4h4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-4"/><path d="M10 12H3m0 0 3-3m-3 3 3 3"/>' }) %}
|
{#
|
||||||
|
MIRRORED ABOUT x=12, i.e. the glyph reads LEAVING rather than entering: the doorway sits on
|
||||||
|
the left and the arrow points out of it, to the right. Every path is the same path reflected
|
||||||
|
(each x becomes 24-x, and each arc's sweep flag flips with it), so the stroke widths, the
|
||||||
|
corner radii and the 24x24 box are untouched — nothing here is a redrawn icon.
|
||||||
|
-#}
|
||||||
|
{% include('themes/footstrap/partials/icon', { body: '<path d="M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4"/><path d="M14 12h7m0 0-3-3m3 3-3 3"/>' }) %}
|
||||||
<span class="fs-label">{{ _('Log out') }}</span>
|
<span class="fs-label">{{ _('Log out') }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# Self-contained static build of lpac for wwand-esim.
|
||||||
|
#
|
||||||
|
# lpac's ES9+ HTTPS leg needs a TLS stack with the full GSMA curve/algorithm
|
||||||
|
# set. OpenWrt's mbedtls is feature-stripped (fails the SM-DP+ TLS) and its
|
||||||
|
# libcurl+OpenSSL drags in ~6.4 MB of libcrypto/libssl. Instead we bundle a
|
||||||
|
# minimal static wolfSSL + a minimal static libcurl straight into the lpac
|
||||||
|
# binary: one ~1 MB self-contained executable, no libcurl/libssl .so at all.
|
||||||
|
#
|
||||||
|
# Proven on the RG650E: full SGP.22 RSP download over the wwand stdio bridge.
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=wwand-lpac
|
||||||
|
PKG_VERSION:=2.3.0
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_LICENSE:=AGPL-3.0-only GPL-3.0-or-later curl
|
||||||
|
PKG_MAINTAINER:=
|
||||||
|
|
||||||
|
# lpac (primary source)
|
||||||
|
PKG_SOURCE:=lpac-$(PKG_VERSION).tar.gz
|
||||||
|
PKG_SOURCE_URL:=https://codeload.github.com/estkme-group/lpac/tar.gz/refs/tags/v$(PKG_VERSION)?
|
||||||
|
PKG_HASH:=skip
|
||||||
|
# unique build dir (the lpac tarball's top dir is lpac-2.3.0, same as the stock
|
||||||
|
# lpac package — extract it ourselves with --strip-components into our own dir)
|
||||||
|
PKG_BUILD_DIR:=$(BUILD_DIR)/wwand-lpac-$(PKG_VERSION)
|
||||||
|
|
||||||
|
# bundled, statically-linked dependencies
|
||||||
|
WOLFSSL_VERSION:=5.9.1-stable
|
||||||
|
CURL_VERSION:=8.21.0
|
||||||
|
|
||||||
|
PKG_BUILD_PARALLEL:=1
|
||||||
|
|
||||||
|
# wolfSSL's SP math (sp_int.c, WOLFSSL_SP_MIPS) carries inline assembly that
|
||||||
|
# MIPS16 cannot encode ("operand 3 must be an immediate expression `addu ...'")
|
||||||
|
PKG_BUILD_FLAGS:=no-mips16
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Download/wolfssl
|
||||||
|
FILE:=wolfssl-$(WOLFSSL_VERSION).tar.gz
|
||||||
|
URL:=https://github.com/wolfSSL/wolfssl/archive/v$(WOLFSSL_VERSION)
|
||||||
|
HASH:=skip
|
||||||
|
endef
|
||||||
|
$(eval $(call Download,wolfssl))
|
||||||
|
|
||||||
|
define Download/curl
|
||||||
|
FILE:=curl-$(CURL_VERSION).tar.xz
|
||||||
|
URL:=https://github.com/curl/curl/releases/download/curl-8_21_0
|
||||||
|
HASH:=skip
|
||||||
|
endef
|
||||||
|
$(eval $(call Download,curl))
|
||||||
|
|
||||||
|
define Package/wwand-lpac
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=WWAN
|
||||||
|
TITLE:=lpac eSIM LPA — self-contained static build (wolfSSL)
|
||||||
|
URL:=https://github.com/estkme-group/lpac
|
||||||
|
# PROVIDES:=lpac so wwand-esim's `+lpac` dependency resolves to this
|
||||||
|
# self-contained build. Do NOT also CONFLICTS:=lpac — when the real `lpac`
|
||||||
|
# package is present in the feed set (CI), CONFLICTS emits a
|
||||||
|
# `depends on PACKAGE_lpac` (scripts/package-metadata.pl mconf_conflicts)
|
||||||
|
# which, together with the PROVIDES `select`, is a Kconfig recursive
|
||||||
|
# dependency that breaks `make defconfig` for every feed consumer. The
|
||||||
|
# vpackage provider model keeps the two `lpac` providers mutually exclusive.
|
||||||
|
PROVIDES:=lpac
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-lpac/description
|
||||||
|
A single self-contained lpac binary for wwand-esim: minimal static wolfSSL
|
||||||
|
(TLS 1.2/1.3, ECDHE, ECDSA+RSA, NIST + Brainpool curves, x25519, AES-GCM,
|
||||||
|
SHA-2; built with WOLFSSL_NO_ASN_STRICT so the GSMA id-rspRole critical
|
||||||
|
extension is accepted) and a minimal static libcurl (HTTPS only) are linked
|
||||||
|
straight into lpac. No libcurl/libssl/libcrypto .so — ~1 MB total instead of
|
||||||
|
the ~6.4 MB that libcurl+OpenSSL pulls in. Only the stdio + curl drivers are
|
||||||
|
built (the wwand stdio bridge relays the eUICC APDUs through wwand's own
|
||||||
|
modem_apdu channel). Drop-in replacement for the stock lpac package.
|
||||||
|
|
||||||
|
Includes patch 110 (backport of upstream #399, the v2.3.0 stdio-driver fix).
|
||||||
|
endef
|
||||||
|
|
||||||
|
PREFIX:=$(PKG_BUILD_DIR)/.bundle
|
||||||
|
WOLFSSL_DIR:=$(PKG_BUILD_DIR)/wolfssl-$(WOLFSSL_VERSION)
|
||||||
|
CURL_DIR:=$(PKG_BUILD_DIR)/curl-$(CURL_VERSION)
|
||||||
|
LPAC_BUILD:=$(PKG_BUILD_DIR)/.lpac-build
|
||||||
|
|
||||||
|
# LTO across the whole bundle (wolfSSL + libcurl + lpac). Fat LTO objects keep
|
||||||
|
# real machine code alongside the IR, so the sub-libs' configure link-tests
|
||||||
|
# (plain, no -flto) still resolve symbols while the final lpac link (-flto) still
|
||||||
|
# optimises across all three. gcc-ar/gcc-ranlib are the LTO-aware archivers.
|
||||||
|
LTO_CFLAGS:=-flto=auto -ffat-lto-objects
|
||||||
|
BUNDLE_AR:=AR="$(TARGET_CROSS)gcc-ar" RANLIB="$(TARGET_CROSS)gcc-ranlib"
|
||||||
|
|
||||||
|
# extract lpac (strip its top dir into our unique build dir), patch it, then
|
||||||
|
# unpack the bundled wolfSSL + curl sources alongside it
|
||||||
|
define Build/Prepare
|
||||||
|
$(INSTALL_DIR) $(PKG_BUILD_DIR)
|
||||||
|
$(TAR) -C $(PKG_BUILD_DIR) --strip-components=1 -xzf $(DL_DIR)/$(PKG_SOURCE)
|
||||||
|
$(Build/Patch)
|
||||||
|
$(TAR) -C $(PKG_BUILD_DIR) -xzf $(DL_DIR)/wolfssl-$(WOLFSSL_VERSION).tar.gz
|
||||||
|
$(TAR) -C $(PKG_BUILD_DIR) -xJf $(DL_DIR)/curl-$(CURL_VERSION).tar.xz
|
||||||
|
endef
|
||||||
|
|
||||||
|
# nothing to autoconf for the top-level (lpac is CMake); the sub-libs are
|
||||||
|
# configured inside Build/Compile
|
||||||
|
Build/Configure:=
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
# 1) minimal static wolfSSL (GSMA-capable)
|
||||||
|
( cd $(WOLFSSL_DIR) && ./autogen.sh )
|
||||||
|
( cd $(WOLFSSL_DIR) && ./configure --host=$(GNU_TARGET_NAME) --prefix=$(PREFIX) \
|
||||||
|
--enable-static --disable-shared \
|
||||||
|
--enable-curl --enable-tls13 --enable-brainpool --enable-ecccustcurves \
|
||||||
|
--enable-curve25519 --disable-dtls --disable-oldtls \
|
||||||
|
--disable-examples --disable-crypttests \
|
||||||
|
CC="$(TARGET_CC)" $(BUNDLE_AR) \
|
||||||
|
CFLAGS="$(TARGET_CFLAGS) $(LTO_CFLAGS) -ffunction-sections -fdata-sections -DWOLFSSL_NO_ASN_STRICT" )
|
||||||
|
+$(MAKE) -C $(WOLFSSL_DIR)
|
||||||
|
$(MAKE) -C $(WOLFSSL_DIR) install
|
||||||
|
|
||||||
|
# 2) minimal static libcurl (HTTPS only) against the bundled wolfSSL
|
||||||
|
# -DSIZEOF_LONG_LONG=8: on 32-bit targets curl_config.h defines
|
||||||
|
# SIZEOF_LONG=4 and nothing else; wolfSSL's CTC_SETTINGS enum (types.h)
|
||||||
|
# then hits "#error bad math long / long long settings". 64-bit targets
|
||||||
|
# take the SIZEOF_LONG==8 branch and never look at it.
|
||||||
|
( cd $(CURL_DIR) && \
|
||||||
|
PKG_CONFIG_PATH="$(PREFIX)/lib/pkgconfig" PKG_CONFIG_LIBDIR="$(PREFIX)/lib/pkgconfig" \
|
||||||
|
./configure --host=$(GNU_TARGET_NAME) --prefix=$(PREFIX) --with-wolfssl=$(PREFIX) \
|
||||||
|
--enable-static --disable-shared \
|
||||||
|
--disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp \
|
||||||
|
--disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap \
|
||||||
|
--disable-smtp --disable-gopher --disable-mqtt --disable-smb --disable-ntlm \
|
||||||
|
--disable-manual --disable-unix-sockets --disable-progress-meter \
|
||||||
|
--without-libpsl --without-zlib --without-nghttp2 --without-brotli \
|
||||||
|
--without-zstd --without-libidn2 --without-librtmp --without-ca-path \
|
||||||
|
--with-ca-bundle=/etc/ssl/certs/ca-certificates.crt \
|
||||||
|
CC="$(TARGET_CC)" $(BUNDLE_AR) \
|
||||||
|
CFLAGS="$(TARGET_CFLAGS) $(LTO_CFLAGS) -ffunction-sections -fdata-sections -DSIZEOF_LONG_LONG=8" \
|
||||||
|
CPPFLAGS="-I$(PREFIX)/include" LDFLAGS="-L$(PREFIX)/lib" LIBS="-lm" )
|
||||||
|
+$(MAKE) -C $(CURL_DIR)
|
||||||
|
$(MAKE) -C $(CURL_DIR) install
|
||||||
|
|
||||||
|
# 3) lpac, statically linked against the two bundled .a
|
||||||
|
rm -rf $(LPAC_BUILD)
|
||||||
|
mkdir -p $(LPAC_BUILD)
|
||||||
|
( cd $(LPAC_BUILD) && PKG_CONFIG_PATH="$(PREFIX)/lib/pkgconfig" cmake $(PKG_BUILD_DIR) \
|
||||||
|
-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=$(ARCH) \
|
||||||
|
-DCMAKE_C_COMPILER="$(TARGET_CROSS)gcc" \
|
||||||
|
-DCMAKE_AR="$(TARGET_CROSS)gcc-ar" -DCMAKE_RANLIB="$(TARGET_CROSS)gcc-ranlib" \
|
||||||
|
-DCMAKE_FIND_ROOT_PATH="$(PREFIX)" -DCMAKE_PREFIX_PATH="$(PREFIX)" \
|
||||||
|
-DCMAKE_C_FLAGS="$(TARGET_CFLAGS) $(LTO_CFLAGS) -ffunction-sections -fdata-sections -I$(PREFIX)/include" \
|
||||||
|
-DCMAKE_EXE_LINKER_FLAGS="$(TARGET_LDFLAGS) $(LTO_CFLAGS) -Wl,--gc-sections -L$(PREFIX)/lib" \
|
||||||
|
-DCMAKE_C_STANDARD_LIBRARIES="-L$(PREFIX)/lib -lwolfssl -lm" \
|
||||||
|
-DLPAC_DYNAMIC_LIBEUICC=OFF \
|
||||||
|
-DLPAC_WITH_APDU_PCSC=OFF -DLPAC_WITH_APDU_AT=OFF \
|
||||||
|
-DLPAC_WITH_APDU_QMI=OFF -DLPAC_WITH_APDU_MBIM=OFF \
|
||||||
|
-DLPAC_WITH_HTTP_CURL=ON \
|
||||||
|
-DCURL_INCLUDE_DIR="$(PREFIX)/include" -DCURL_LIBRARY="$(PREFIX)/lib/libcurl.a" )
|
||||||
|
+$(MAKE) -C $(LPAC_BUILD)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-lpac/install
|
||||||
|
$(INSTALL_DIR) $(1)/usr/lib
|
||||||
|
$(INSTALL_BIN) $(LPAC_BUILD)/output/lpac $(1)/usr/lib/lpac
|
||||||
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
|
$(INSTALL_BIN) ./files/lpac.sh $(1)/usr/bin/lpac
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,wwand-lpac))
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# thin wrapper so `lpac` on PATH resolves to the self-contained static binary
|
||||||
|
exec /usr/lib/lpac "$@"
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
Backport of upstream lpac PR #399 ("fix(driver): 'json_request' interface was
|
||||||
|
changed in b23f0e4", commit 977c324, merged 2025-11-02) to the v2.3.0 release.
|
||||||
|
json_print()/json_request() were changed to return true on success, but
|
||||||
|
apdu/stdio.c's connect/logic_channel_open/transmit still checked
|
||||||
|
`if (json_request(...)) return -1;` — bailing with -1 the moment a request is
|
||||||
|
sent successfully, so euicc_init dies at connect before reading any reply.
|
||||||
|
Drop this patch once lpac ships a release later than v2.3.0 that includes #399.
|
||||||
|
|
||||||
|
--- a/driver/apdu/stdio.c
|
||||||
|
+++ b/driver/apdu/stdio.c
|
||||||
|
@@ -160,7 +160,7 @@
|
||||||
|
static int apdu_interface_connect(struct euicc_ctx *ctx) {
|
||||||
|
int ecode;
|
||||||
|
|
||||||
|
- if (json_request("connect", NULL, 0)) {
|
||||||
|
+ if (!json_request("connect", NULL, 0)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -183,7 +183,7 @@
|
||||||
|
static int apdu_interface_logic_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len) {
|
||||||
|
int ecode;
|
||||||
|
|
||||||
|
- if (json_request("logic_channel_open", aid, aid_len)) {
|
||||||
|
+ if (!json_request("logic_channel_open", aid, aid_len)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -209,7 +209,7 @@
|
||||||
|
uint32_t tx_len) {
|
||||||
|
int ecode;
|
||||||
|
|
||||||
|
- if (json_request("transmit", tx, tx_len)) {
|
||||||
|
+ if (!json_request("transmit", tx, tx_len)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
+346
@@ -0,0 +1,346 @@
|
|||||||
|
# OpenWrt package definitions for the wwand connection manager.
|
||||||
|
# One source package (git: ddimension/wwand); the ucode side is split into a
|
||||||
|
# backend-neutral base plus per-backend packages so an install carries only the
|
||||||
|
# control protocols it needs:
|
||||||
|
# wwand - daemon + shared core + codec/client + native wwand_io.so
|
||||||
|
# transport module (no backend on its own)
|
||||||
|
# wwand-qmi - QMI backend (DEPENDS wwand)
|
||||||
|
# wwand-mbim - MBIM backend (DEPENDS wwand-qmi: the MBIM
|
||||||
|
# QMI-over-MBIM passthrough reuses
|
||||||
|
# qmi_backend)
|
||||||
|
# wwand-ncm - NCM/ECM backend (DEPENDS wwand)
|
||||||
|
# wwand-mhi - PCIe/MHI transport + drivers (DEPENDS wwand; add a backend)
|
||||||
|
# wwand-esim - eSIM management (DEPENDS wwand-qmi + wwand-lpac)
|
||||||
|
# A typical QMI router installs `wwand-qmi` (which pulls in `wwand`); add
|
||||||
|
# `wwand-mbim` / `wwand-ncm` for those modems, `wwand-mhi` for a PCIe/MHI modem.
|
||||||
|
# The ucode tree ships precompiled to bytecode by default — built by the
|
||||||
|
# repo-root CMakeLists.txt alongside wwand_io.so (dev opt-out:
|
||||||
|
# CONFIG_WWAND_UCODE_SOURCE).
|
||||||
|
#
|
||||||
|
# NOTE: the split needs the ucode source that lazy-loads every backend
|
||||||
|
# (qmi_lazy.uc + the backend-neutral daemon). Bump PKG_SOURCE_VERSION (and
|
||||||
|
# PKG_SOURCE_DATE / PKG_MIRROR_HASH) to a wwand commit that includes them.
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=wwand
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_SOURCE_PROTO:=git
|
||||||
|
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||||
|
PKG_SOURCE_VERSION:=61f4c2dc728d70e490c94b3e1d8cb2262c262ebb
|
||||||
|
PKG_SOURCE_DATE:=2026-08-17
|
||||||
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
|
PKG_LICENSE:=GPL-2.0-only
|
||||||
|
PKG_MAINTAINER:=
|
||||||
|
|
||||||
|
# host ucode is the bytecode compiler for the precompile step (below). It is
|
||||||
|
# built from the same ucode revision as the on-device runtime, so the bytecode
|
||||||
|
# it emits always matches the target interpreter.
|
||||||
|
PKG_BUILD_DEPENDS:=ucode/host
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
include $(INCLUDE_DIR)/cmake.mk
|
||||||
|
|
||||||
|
# One cmake build (repo-root CMakeLists.txt) produces both the native
|
||||||
|
# wwand_io.so (io/ subdir) and — by default — the ucode tree compiled to
|
||||||
|
# bytecode, one compiler invocation per file with explicit source lists (the
|
||||||
|
# whole mechanism, incl. the invariants it enforces, is documented in the
|
||||||
|
# wwand repo's CMakeLists.txt). The bytecode compiler is the SDK's host ucode
|
||||||
|
# (PKG_BUILD_DEPENDS:=ucode/host) — same ucode revision as the target runtime,
|
||||||
|
# which bytecode is locked to.
|
||||||
|
CMAKE_BINARY_SUBDIR:=build
|
||||||
|
|
||||||
|
CMAKE_OPTIONS += \
|
||||||
|
-DUCODE_COMPILER=$(STAGING_DIR_HOSTPKG)/bin/ucode \
|
||||||
|
-DUCODE_PRECOMPILE=$(if $(CONFIG_WWAND_UCODE_SOURCE),OFF,ON)
|
||||||
|
|
||||||
|
# every install section takes the ucode tree from here: the bytecode output of
|
||||||
|
# the cmake build, or the plain sources when CONFIG_WWAND_UCODE_SOURCE is set.
|
||||||
|
WWAND_UCODE=$(if $(CONFIG_WWAND_UCODE_SOURCE),$(PKG_BUILD_DIR)/src-ucode,$(PKG_BUILD_DIR)/$(CMAKE_BINARY_SUBDIR)/ucode/wwand)
|
||||||
|
|
||||||
|
UCDIR:=/usr/share/ucode/wwand
|
||||||
|
|
||||||
|
# Base-package ucode set, listed explicitly per install dir. No glob-then-`rm`:
|
||||||
|
# every module is owned by exactly one package, so a new backend file can never
|
||||||
|
# silently ship in the base too. The per-backend install lists below carry the
|
||||||
|
# QMI/MBIM/NCM/eSIM modules; main.uc/wwandctl.uc install as executables.
|
||||||
|
WWAND_BASE_UC:=apndb.uc atcmd.uc atcmd_parse.uc atport.uc backend.uc board.uc \
|
||||||
|
client.uc config.uc config_check.uc context_common.uc context_monitor_qmi.uc \
|
||||||
|
ctx_settings.uc daemon.uc datapath_qmi.uc discovery.uc hwops.uc log.uc \
|
||||||
|
modem_common.uc modem_init_qmi.uc modem_quirks.uc modeswitch.uc ncm_vendors.uc \
|
||||||
|
netlink.uc netsel_ops.uc protocol_switch.uc reconnect.uc recovery.uc regdetail.uc \
|
||||||
|
sim.uc sim_plmn.uc simops.uc sms.uc sms_pdu.uc telemetry_mbim.uc telemetry_ncm.uc \
|
||||||
|
telemetry_qmi.uc transport.uc ubus.uc
|
||||||
|
WWAND_BASE_CODEC:=arfcn_bands.uc hex.uc qmux.uc tlv.uc
|
||||||
|
WWAND_BASE_SCHEMA:=ctl.uc dms.uc dsd.uc loc.uc loc_lazy.uc merge.uc nas.uc rat.uc \
|
||||||
|
uim.uc wda.uc wds.uc wms.uc wms_lazy.uc
|
||||||
|
|
||||||
|
# DEVELOPERS: enable CONFIG_WWAND_UCODE_SOURCE (menuconfig, under this package)
|
||||||
|
# to ship readable .uc source instead of bytecode — for editing modules live
|
||||||
|
# under /usr/share/ucode/wwand and for source-line tracebacks; bytecode
|
||||||
|
# tracebacks report offsets only.
|
||||||
|
PKG_CONFIG_DEPENDS:=CONFIG_WWAND_UCODE_SOURCE
|
||||||
|
|
||||||
|
define Package/wwand/config
|
||||||
|
config WWAND_UCODE_SOURCE
|
||||||
|
bool "Ship ucode sources instead of precompiled bytecode"
|
||||||
|
depends on PACKAGE_wwand
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
wwand normally ships its ucode tree precompiled to bytecode
|
||||||
|
(faster daemon start, no parse step on the device). Enable this
|
||||||
|
for development builds: the readable sources are installed
|
||||||
|
instead, runtime tracebacks carry source line numbers and the
|
||||||
|
modules can be edited in place on the device.
|
||||||
|
endef
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# base: daemon + framework + codec/client + shared core. No backend on its own;
|
||||||
|
# the daemon loads whichever wwand-qmi/-mbim/-ncm package is present.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
define Package/wwand
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=WWAN
|
||||||
|
TITLE:=Event-driven cellular connection manager (backend-neutral core)
|
||||||
|
DEPENDS:=+ucode +ucode-mod-fs +ucode-mod-struct +ucode-mod-uloop \
|
||||||
|
+ucode-mod-ubus +ucode-mod-uci +ucode-mod-rtnl
|
||||||
|
# the native wwand_io transport module ships inside this package (it is
|
||||||
|
# wwand-private, always version-locked to the ucode side). PROVIDES keeps
|
||||||
|
# configs/upgrades from the era of the separate ucode-mod-wwand-io working.
|
||||||
|
PROVIDES:=ucode-mod-wwand-io
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand/description
|
||||||
|
wwand is an event-driven connection manager daemon for cellular modems,
|
||||||
|
supporting multiple modems and multiple parallel PDP contexts. This base
|
||||||
|
package carries the daemon, the netifd proto shim, the QMI codec (qmux/tlv +
|
||||||
|
service schemas), the generic service client, the SIM/APDU layer and the
|
||||||
|
shared modem/context core. Install a backend package (wwand-qmi, wwand-mbim
|
||||||
|
or wwand-ncm) for the control protocols your modems use.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand/install
|
||||||
|
# native transport module (built by the cmake step from io/)
|
||||||
|
$(INSTALL_DIR) $(1)/usr/lib/ucode
|
||||||
|
$(INSTALL_BIN) $(CMAKE_BINARY_DIR)/io/wwand_io.so $(1)/usr/lib/ucode/
|
||||||
|
# base ucode tree — explicit per-file lists (see WWAND_BASE_* above), so the
|
||||||
|
# base package owns exactly its own modules and nothing a backend ships.
|
||||||
|
$(INSTALL_DIR) $(1)$(UCDIR)/codec/schema
|
||||||
|
$(INSTALL_DATA) $(addprefix $(WWAND_UCODE)/,$(WWAND_BASE_UC)) $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(addprefix $(WWAND_UCODE)/codec/,$(WWAND_BASE_CODEC)) $(1)$(UCDIR)/codec/
|
||||||
|
$(INSTALL_DATA) $(addprefix $(WWAND_UCODE)/codec/schema/,$(WWAND_BASE_SCHEMA)) $(1)$(UCDIR)/codec/schema/
|
||||||
|
# daemon + CLI as executables (not under $(UCDIR))
|
||||||
|
$(INSTALL_DIR) $(1)/usr/sbin
|
||||||
|
$(INSTALL_BIN) $(WWAND_UCODE)/main.uc $(1)/usr/sbin/wwand
|
||||||
|
# wwandctl: the human-friendly CLI front-end over the ubus API
|
||||||
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
|
$(INSTALL_BIN) $(WWAND_UCODE)/wwandctl.uc $(1)/usr/bin/wwandctl
|
||||||
|
$(INSTALL_DIR) $(1)/lib/netifd/proto
|
||||||
|
# the shim registers `wwand`; the legacy `qmi` alias only when the global
|
||||||
|
# `option takeover` is set (default off, so uqmi keeps `proto qmi`). Install
|
||||||
|
# under the current name — the historical qmi.sh belonged to uqmi
|
||||||
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand-proto.sh $(1)/lib/netifd/proto/wwand.sh
|
||||||
|
$(INSTALL_DIR) $(1)/usr/libexec/wwand
|
||||||
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand-migrate $(1)/usr/libexec/wwand/migrate
|
||||||
|
# migration is user-triggered (LuCI modem list / the migrate CLI); the
|
||||||
|
# uci-defaults hook auto-migrates on install/upgrade ONLY when the global
|
||||||
|
# `option takeover` is set (default off — wwand does not touch existing configs)
|
||||||
|
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||||
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand.defaults $(1)/etc/uci-defaults/99-wwand-migrate
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/files/wwand.init $(1)/etc/init.d/wwand
|
||||||
|
# no /etc/config/wwand for new installs — all config lives in
|
||||||
|
# /etc/config/network now (existing files survive upgrade and are still read).
|
||||||
|
# files/wwand.config is kept in the source tree only as a documented example.
|
||||||
|
$(INSTALL_DIR) $(1)/etc/hotplug.d/usbmisc
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug $(1)/etc/hotplug.d/usbmisc/20-wwand
|
||||||
|
# net hotplug: NCM modems (no cdc-wdm) + re-enumeration after a mode switch
|
||||||
|
$(INSTALL_DIR) $(1)/etc/hotplug.d/net
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.net $(1)/etc/hotplug.d/net/20-wwand
|
||||||
|
# tty hotplug: AT ports appearing after the datapath netdev (vendor-serial
|
||||||
|
# new_id bind / late kmodloader) re-kick a modem parked in no_at_port backoff
|
||||||
|
$(INSTALL_DIR) $(1)/etc/hotplug.d/tty
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.tty $(1)/etc/hotplug.d/tty/20-wwand
|
||||||
|
# NOTE: the kernel-wwan-subsystem hotplug (/etc/hotplug.d/wwan/20-wwand) is
|
||||||
|
# NOT installed here — it belongs to wwand-mhi, which also pulls the MHI
|
||||||
|
# drivers that create /sys/class/wwan in the first place (procd only arms a
|
||||||
|
# subsystem whose hotplug dir exists). USB modems never need it.
|
||||||
|
endef
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# wwand-qmi: the QMI backend (native qmux over /dev/cdc-wdmX).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
define Package/wwand-qmi
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=WWAN
|
||||||
|
TITLE:=QMI backend for wwand
|
||||||
|
DEPENDS:=+wwand +kmod-usb-net-qmi-wwan +kmod-rmnet
|
||||||
|
# coexists with the stock OpenWrt QMI stack (uqmi):
|
||||||
|
# by default wwand claims only `proto wwand` interfaces, so both can be
|
||||||
|
# installed. Set the global `option takeover '1'` (or migrate interfaces from
|
||||||
|
# the LuCI modem list) to hand `proto qmi` interfaces to wwand.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-qmi/description
|
||||||
|
QMI control backend for wwand: talks QMI natively over /dev/cdc-wdmX (qmi_wwan
|
||||||
|
driver) with QMAP multiplexing (rmnet), without spawning uqmi/qmicli. This is
|
||||||
|
the common case for most cellular routers.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-qmi/install
|
||||||
|
$(INSTALL_DIR) $(1)$(UCDIR)
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/modem.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/context.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/qmi_backend.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/callend.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/qmi_lazy.uc $(1)$(UCDIR)/
|
||||||
|
endef
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# wwand-mbim: the MBIM backend. Depends on wwand-qmi because the QMI-over-MBIM
|
||||||
|
# passthrough runs the QMI stack (qmi_backend) over the open MBIM channel.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
define Package/wwand-mbim
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=WWAN
|
||||||
|
TITLE:=MBIM backend for wwand
|
||||||
|
DEPENDS:=+wwand-qmi +kmod-usb-net-cdc-mbim
|
||||||
|
# coexists with the stock OpenWrt MBIM stack (netifd `mbim` proto, package
|
||||||
|
# umbim): wwand manages a cdc_mbim modem only once its interface has been
|
||||||
|
# migrated to `proto wwand` (LuCI modem list / migrate CLI), so both stacks can
|
||||||
|
# be installed side by side.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-mbim/description
|
||||||
|
MBIM control backend for wwand (cdc_mbim driver): native MS Basic Connect (+
|
||||||
|
Extensions v2/v3) plus a QMI-over-MBIM passthrough that tunnels the whole QMI
|
||||||
|
stack over the open MBIM channel for full CDC-level telemetry/config. The
|
||||||
|
passthrough reuses qmi_backend, hence the dependency on wwand-qmi.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-mbim/install
|
||||||
|
$(INSTALL_DIR) $(1)$(UCDIR)/codec/mbim_schema
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/modem_mbim.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/context_mbim.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/mbim_backend.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/mbim_client.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/qmi_over_mbim.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/mbim_lazy.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/codec/mbim.uc $(1)$(UCDIR)/codec/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/codec/mbim_schema/*.uc $(1)$(UCDIR)/codec/mbim_schema/
|
||||||
|
endef
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# wwand-ncm: the NCM/ECM backend (cdc_ncm / cdc_ether, AT-controlled).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
define Package/wwand-ncm
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=WWAN
|
||||||
|
TITLE:=NCM/ECM backend for wwand
|
||||||
|
DEPENDS:=+wwand +kmod-usb-net-cdc-ncm +kmod-usb-net-cdc-ether
|
||||||
|
# coexists with the stock OpenWrt NCM stack (netifd `ncm` proto, package
|
||||||
|
# comgt-ncm): wwand drives an AT/NCM modem only once its interface has been
|
||||||
|
# migrated to `proto wwand` (LuCI modem list / migrate CLI), so both stacks can
|
||||||
|
# be installed side by side.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-ncm/description
|
||||||
|
NCM/ECM control backend for wwand: an AT-controlled datapath over a plain
|
||||||
|
cdc_ncm / cdc_ether netdev, for modems that expose no cdc-wdm control device.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-ncm/install
|
||||||
|
$(INSTALL_DIR) $(1)$(UCDIR)
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/modem_ncm.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/context_ncm.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/ncm_lazy.uc $(1)$(UCDIR)/
|
||||||
|
endef
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# wwand-mhi: PCIe/MHI transport bundle. Modems on the kernel `wwan` subsystem
|
||||||
|
# (Foxconn T99W175/SDX, Quectel RM5xx-AE, Telit FN990, …) expose their QMI/MBIM
|
||||||
|
# control port under /sys/class/wwan — not usbmisc/cdc-wdm — and need the MHI
|
||||||
|
# driver stack instead of the USB kmods. This package pulls those drivers and
|
||||||
|
# ships the wwan-subsystem hotplug that binds such a modem on cold-plug. It is
|
||||||
|
# transport-only and backend-neutral: install ALONGSIDE wwand-qmi (QMI over MHI,
|
||||||
|
# rmnet data) or wwand-mbim (MBIM over MHI) for the control protocol.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
define Package/wwand-mhi
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=WWAN
|
||||||
|
TITLE:=PCIe/MHI transport + drivers for wwand
|
||||||
|
DEPENDS:=+wwand +kmod-mhi-pci-generic +kmod-mhi-wwan-ctrl \
|
||||||
|
+kmod-mhi-wwan-mbim +kmod-mhi-net
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-mhi/description
|
||||||
|
Support for cellular modems attached over PCIe/MHI (Foxconn T99W175 and
|
||||||
|
similar), whose control ports appear on the kernel wwan subsystem. Pulls the
|
||||||
|
MHI bus/PCI/control/data drivers and installs the wwan-subsystem hotplug so
|
||||||
|
wwand binds these modems on boot. Install together with a control backend
|
||||||
|
(wwand-qmi or wwand-mbim).
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-mhi/install
|
||||||
|
# wwan-subsystem hotplug: MHI modems expose their control port under
|
||||||
|
# /sys/class/wwan (/dev/wwanXqmiN|mbimN). procd only dispatches the `wwan`
|
||||||
|
# subsystem once this directory exists — so it ships with the MHI drivers,
|
||||||
|
# not with the base package.
|
||||||
|
$(INSTALL_DIR) $(1)/etc/hotplug.d/wwan
|
||||||
|
$(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.wwan $(1)/etc/hotplug.d/wwan/20-wwand
|
||||||
|
endef
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# wwand-esim: eSIM (eUICC) profile management. Needs a QMI UIM/APDU channel.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
define Package/wwand-esim
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=WWAN
|
||||||
|
TITLE:=eSIM (eUICC) profile management for wwand
|
||||||
|
# Needs an lpac binary at /usr/bin/lpac: either the generic openwrt-packages
|
||||||
|
# lpac, or our self-contained static wwand-lpac (bundled wolfSSL+libcurl, no
|
||||||
|
# OpenSSL/mbedtls .so — it PROVIDES lpac, so it satisfies +lpac too). lpac
|
||||||
|
# drives the SM-DP+ download/notification (ES9+ HTTPS on the router); the
|
||||||
|
# daemon bridges its stdio APDU protocol inline. APDU access uses the modem
|
||||||
|
# UIM channel, so it also needs the QMI backend.
|
||||||
|
DEPENDS:=+wwand-qmi +lpac
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-esim/description
|
||||||
|
Optional ES10c (SGP.22) profile management for wwand: EID, profile list,
|
||||||
|
enable/disable/delete over the modem UIM APDU channel. SM-DP+ profile
|
||||||
|
downloads and notification acknowledgements are driven by lpac: its ES10
|
||||||
|
APDUs are relayed through wwand's own APDU channel (ubus modem_apdu) by the
|
||||||
|
stdio bridge, so wwand stays the sole owner of the modem — no separate AT
|
||||||
|
port. lpac runs the ES9+ HTTPS on the router; no dedicated modem APN is
|
||||||
|
required.
|
||||||
|
|
||||||
|
The download is handled by an lpac binary (the +lpac dependency): either the
|
||||||
|
generic openwrt-packages lpac, or the self-contained wwand-lpac (which PROVIDES
|
||||||
|
lpac) — a single ~0.9 MB binary with a minimal static wolfSSL + libcurl linked
|
||||||
|
in, needing no libcurl/libssl/libcrypto .so (vs the ~6.4 MB stock libcurl+
|
||||||
|
OpenSSL pulls in) and carrying the GSMA-capable TLS itself. Either works via
|
||||||
|
/usr/bin/lpac; the stdio APDU bridge needs lpac >= 2.3.0 (upstream PR #399).
|
||||||
|
See wwand-lpac/Makefile.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/wwand-esim/install
|
||||||
|
$(INSTALL_DIR) $(1)$(UCDIR)
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/esim.uc $(1)$(UCDIR)/
|
||||||
|
$(INSTALL_DATA) $(WWAND_UCODE)/esim_bridge.uc $(1)$(UCDIR)/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,wwand))
|
||||||
|
$(eval $(call BuildPackage,wwand-qmi))
|
||||||
|
$(eval $(call BuildPackage,wwand-mbim))
|
||||||
|
$(eval $(call BuildPackage,wwand-ncm))
|
||||||
|
$(eval $(call BuildPackage,wwand-mhi))
|
||||||
|
$(eval $(call BuildPackage,wwand-esim))
|
||||||
Reference in new issue
Block a user