🐶 Sync 2026-06-22 04:24:22

This commit is contained in:
github-actions[bot] 2026-06-22 04:24:22 +08:00
parent af01fe89d2
commit d93d01a89d
13 changed files with 1706 additions and 6 deletions

176
luci-app-airoha-npu/LICENSE Normal file
View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,14 @@
# This is free software, licensed under the Apache License, Version 2.0 .
include $(TOPDIR)/rules.mk
PKG_VERSION:=1.0.1
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
LUCI_TITLE:=LuCI Airoha SoC Status (NPU, CPU, Frame Engine)
LUCI_DEPENDS:=+luci-base @TARGET_airoha
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,165 @@
# luci-app-airoha-npu
Real-time monitoring and management dashboard for the Airoha AN7581 SoC on OpenWrt. Covers NPU offload, CPU frequency, WiFi band health, Frame Engine internals, and PPE flow tables.
**[Download](https://github.com/rchen14b/luci-app-airoha-npu/releases/latest)**
![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)
![OpenWrt](https://img.shields.io/badge/OpenWrt-24.10%2B-brightgreen.svg)
![Version](https://img.shields.io/badge/version-1.0.1-orange.svg)
[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow?style=flat&logo=buy-me-a-coffee)](https://buymeacoffee.com/rchen14b)
## Screenshots
### CPU Frequency & Overclock
![CPU Frequency](screenshots/cpu-frequency.png)
### NPU & Offload Engine
![NPU & Offload Engine](screenshots/npu-offload-engine.png)
### PPE Flow Offload Table
![PPE Flow Table](screenshots/ppe-flow-table.png)
## Features
### CPU Frequency Management
- Current frequency display with visual bar graph
- Governor selection (performance, ondemand, schedutil, etc.)
- Max frequency selection from available OPP entries
- Direct PLL overclock control (500-1600 MHz) with hardware register programming
- Overclock detection and warning for unstable frequencies
### NPU & Offload Engine
- NPU firmware version (TLB format), clock frequency, core count
- NPU load status (active/inactive) and reserved memory regions
- WiFi token pool health indicator with in-flight count
- PPE flow offload summary (bound / total entries)
- Per-band WiFi status cards (2.4 GHz, 5 GHz, 6 GHz):
- Client count and link health indicator (Good / Fair / Poor)
- NPU vs DMA path badge
- TX retry rate percentage with color-coded thresholds
### Frame Engine Visualization
- **PSE Shared Buffer** usage bar (congestion indicator)
- **GDM port cards** with live TX/RX packet counters and drop counts:
- GDM1: Internal Switch (1G LAN3/4)
- GDM2: WAN (USXGMII 10G)
- GDM4: LAN2 (USXGMII 10G)
- **CDM offload ratio** bars — HW-forwarded (PPE) vs CPU-path packets
- **PSE Port Queue Status** grid (P0-P9) with IQ/OQ queue depths and drop counts
### PPE Flow Offload Table
- First 100 PPE entries with state (BND/UNB), type (IPv4/IPv6/L2B), 5-tuple, and MAC addresses
- Auto-refreshes every 5 seconds
### Theme Support
- Auto-detects dark/light mode by sampling page background luminance at runtime
- Works with Glass, Bootstrap, Bootstrap-dark, and any LuCI theme
- No hardcoded colors — uses CSS custom properties throughout
## Requirements
- OpenWrt with LuCI (24.10+)
- Airoha AN7581 target (`@TARGET_airoha`)
- PPE debugfs (`/sys/kernel/debug/ppe/entries`)
- **`devmem`** busybox applet — required for Frame Engine register access and CPU overclock (`CONFIG_BUSYBOX_DEFAULT_DEVMEM=y`)
- WiFi token_info debugfs for per-band WiFi stats (`/sys/kernel/debug/ieee80211/phy0/mt76/token_info`)
- Optional: [air_tools](https://github.com/merbanan/air_tools) scripts for additional Frame Engine debugging
## Installation
### From OpenWrt build
```sh
# Add to your build tree
git clone https://github.com/rchen14b/luci-app-airoha-npu.git package/luci-app-airoha-npu
# Enable in menuconfig
make menuconfig
# Navigate to: LuCI -> Applications -> luci-app-airoha-npu
# Build
make package/luci-app-airoha-npu/compile V=s
```
### Manual install (dev)
```sh
# Copy files to router
scp root/usr/libexec/rpcd/luci.airoha_npu root@router:/usr/libexec/rpcd/
scp root/usr/share/luci/menu.d/luci-app-airoha-npu.json root@router:/usr/share/luci/menu.d/
scp root/usr/share/rpcd/acl.d/luci-app-airoha-npu.json root@router:/usr/share/rpcd/acl.d/
scp htdocs/luci-static/resources/view/airoha_npu/status.js root@router:/www/luci-static/resources/view/airoha_npu/
# Set permissions and restart
ssh root@router 'chmod +x /usr/libexec/rpcd/luci.airoha_npu && /etc/init.d/rpcd restart'
```
## Data Sources
| Data | Source | Required |
|------|--------|----------|
| NPU status | `/sys/bus/platform/drivers/airoha-npu/`, `dmesg` | Yes |
| CPU frequency | `/sys/devices/system/cpu/cpufreq/policy0/` | Yes |
| Overclock PLL | `devmem` registers (0x1fa202b4, 0x1fa202b8) | devmem |
| PPE entries | `/sys/kernel/debug/ppe/{entries,bind}` | Yes |
| WiFi token pool | `/sys/kernel/debug/ieee80211/phy0/mt76/token_info` | Optional |
| WiFi station stats | `iw dev <iface> station dump` | Optional |
| Frame Engine (GDM/CDM/PSE) | `devmem` registers (0x1fb50xxx-0x1fb53xxx) | devmem |
## Project Structure
```
luci-app-airoha-npu/
├── Makefile # OpenWrt package build
├── htdocs/
│ └── luci-static/resources/view/airoha_npu/
│ └── status.js # LuCI JavaScript view
├── root/
│ └── usr/
│ ├── libexec/rpcd/
│ │ └── luci.airoha_npu # RPC backend (shell)
│ └── share/
│ ├── luci/menu.d/
│ │ └── luci-app-airoha-npu.json # Menu config
│ └── rpcd/acl.d/
│ └── luci-app-airoha-npu.json # ACL permissions
└── screenshots/
```
## RPC Methods
| Method | Description | Parameters |
|--------|-------------|------------|
| `getStatus` | NPU, CPU, PPE summary | — |
| `getPpeEntries` | PPE flow table (first 100) | — |
| `getTokenInfo` | WiFi token pool & station stats | — |
| `getFrameEngine` | PSE/GDM/CDM register counters | — |
| `setGovernor` | Change CPU governor | `governor` |
| `setMaxFreq` | Set CPU max frequency | `freq` (kHz) |
| `setOverclock` | Direct PLL frequency set | `freq_mhz` |
## Version History
### v1.0.1
- Unified Frame Engine diagram with architectural layout matching AN7581 data paths
- WiFi band cards moved into CDM4/WDMA section (reflects actual WiFi DMA path)
- Added NPU and PPE Engine cards with live status and flow counts
- Token pool and PPE flows removed from summary table (now in Frame Engine view)
- Fixed GDM4 register addresses (0x2500, not 0x3500)
- Improved compact WiFi band chip styling
### v1.0.0
- CPU frequency management with governor and overclock controls
- NPU & Offload Engine monitoring with WiFi band cards
- Frame Engine visualization (GDM ports, CDM offload ratio, PSE queues)
- PPE flow offload table with auto-refresh
- Theme-adaptive dark/light mode detection
## License
Apache-2.0
## Author
Ryan Chen — Created for W1700K router (Airoha AN7581 + MT7996 BE19000)

View File

@ -0,0 +1,483 @@
'use strict';
'require view';
'require poll';
'require rpc';
'require ui';
var callNpuStatus = rpc.declare({ object: 'luci.airoha_npu', method: 'getStatus' });
var callPpeEntries = rpc.declare({ object: 'luci.airoha_npu', method: 'getPpeEntries' });
var callTokenInfo = rpc.declare({ object: 'luci.airoha_npu', method: 'getTokenInfo' });
var callFrameEngine = rpc.declare({ object: 'luci.airoha_npu', method: 'getFrameEngine' });
var callSetGovernor = rpc.declare({ object: 'luci.airoha_npu', method: 'setGovernor', params: ['governor'] });
var callSetMaxFreq = rpc.declare({ object: 'luci.airoha_npu', method: 'setMaxFreq', params: ['freq'] });
var callSetOverclock = rpc.declare({ object: 'luci.airoha_npu', method: 'setOverclock', params: ['freq_mhz'] });
/* ── Theme-adaptive CSS ── */
var themeCSS = '\
.soc-card{background:var(--soc-card-bg);border:1px solid var(--soc-border);border-radius:8px;padding:14px;transition:border-color .3s}\
.soc-card-accent{border-left-width:3px;border-left-style:solid}\
.soc-muted{color:var(--soc-muted)}\
.soc-text{color:var(--soc-text)}\
.soc-label{font-size:11px;color:var(--soc-muted)}\
.soc-bar-track{background:var(--soc-bar-track);border-radius:4px;overflow:hidden}\
.soc-pse-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:6px}\
.soc-pse-cell{background:var(--soc-card-bg);border:1px solid var(--soc-border);border-radius:5px;padding:6px 8px;font-size:12px}\
.soc-band-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:10px}\
.soc-gdm-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:10px}\
.soc-cdm-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:10px}\
';
function isDarkMode() {
// Sample multiple elements to get a reliable reading
var els = [document.body, document.querySelector('.main-content'), document.querySelector('#maincontent'), document.querySelector('.cbi-map')];
for (var i = 0; i < els.length; i++) {
if (!els[i]) continue;
var bg = window.getComputedStyle(els[i]).backgroundColor;
var m = bg.match(/\d+/g);
if (m && m.length >= 3) {
var a = m.length >= 4 ? parseFloat(m[3]) : 1;
if (a < 0.1) continue; // transparent, skip
var lum = (parseInt(m[0]) * 299 + parseInt(m[1]) * 587 + parseInt(m[2]) * 114) / 1000;
return lum < 128;
}
}
// Fallback: check if any known dark theme stylesheet is loaded
var sheets = document.querySelectorAll('link[href*="dark"], link[href*="glass"]');
return sheets.length > 0;
}
var _lastDarkMode = null;
function injectCSS() {
var el = document.getElementById('soc-theme-css');
if (!el) { el = document.createElement('style'); el.id = 'soc-theme-css'; document.head.appendChild(el); }
var dark = isDarkMode();
if (dark === _lastDarkMode) return;
_lastDarkMode = dark;
var vars = dark
? ':root{--soc-card-bg:#1e1e1e;--soc-border:#333;--soc-muted:#999;--soc-text:#e0e0e0;--soc-bar-track:#333}'
: ':root{--soc-card-bg:#fff;--soc-border:#d0d0d0;--soc-muted:#666;--soc-text:#222;--soc-bar-track:#e0e0e0}';
el.textContent = themeCSS + vars;
}
/* ── Helpers ── */
var bandInfo = [
{ name: '2.4 GHz', accent: '#ff9800' },
{ name: '5 GHz', accent: '#2196f3' },
{ name: '6 GHz', accent: '#9c27b0' }
];
var psePortMap = [
{ name: 'CDM1', label: 'CPU DMA 1', color: '#607d8b' },
{ name: 'GDM1', label: 'Switch 1G', color: '#ff9800' },
{ name: 'GDM2', label: 'WAN 10G', color: '#4caf50' },
{ name: 'GDM3', label: 'GDM3', color: '#607d8b' },
{ name: 'PPE1', label: 'PPE Eng 1', color: '#2196f3' },
{ name: 'CDM2', label: 'CPU DMA 2', color: '#607d8b' },
{ name: 'CDM3', label: 'CDM3', color: '#607d8b' },
{ name: 'CDM4', label: 'WDMA WiFi', color: '#9c27b0' },
{ name: 'PPE2', label: 'PPE Eng 2', color: '#2196f3' },
{ name: 'GDM4', label: 'LAN2 10G', color: '#4caf50' }
];
function fmtFreq(khz) { return (!khz || khz === 0) ? 'N/A' : (khz / 1000).toFixed(0) + ' MHz'; }
function fmtK(n) {
if (!n || n === 0) return '0';
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
return n.toString();
}
function calcTotalMem(regions) {
var t = 0;
(regions || []).forEach(function(r) {
var m = (r.size || '').match(/(\d+)\s*(KiB|MiB|GiB)/i);
if (m) { var s = parseInt(m[1]); var u = m[2][0].toUpperCase(); t += u === 'G' ? s*1048576 : u === 'M' ? s*1024 : s; }
});
return t >= 1024 ? (t/1024).toFixed(0)+' MiB' : t+' KiB';
}
function tokenHealth(c, s) {
if (!s) return { text: 'N/A', color: '#888' };
var p = c/s*100;
return p < 50 ? { text:'Healthy', color:'#4caf50' } : p < 80 ? { text:'Warning', color:'#ff9800' } : { text:'Critical', color:'#f44336' };
}
function getBandStats(ti, b) {
var c = Array.isArray(ti.station_counts) ? ti.station_counts : [];
for (var i=0;i<c.length;i++) if (c[i].band===b) return c[i];
return { band:b, count:0, tx_packets:0, tx_retries:0 };
}
function getTxQueue(ti, b) {
var q = Array.isArray(ti.tx_queues) ? ti.tx_queues : [];
for (var i=0;i<q.length;i++) if (q[i].band===b) return q[i];
return null;
}
function bandHealth(s) {
if (!s || s.count===0) return { text:'No clients', color:'#888' };
if (!s.tx_packets) return { text:'Idle', color:'#888' };
var r = s.tx_retries/(s.tx_packets+s.tx_retries);
return r>0.5 ? {text:'Poor',color:'#f44336'} : r>0.2 ? {text:'Fair',color:'#ff9800'} : {text:'Good',color:'#4caf50'};
}
function retryPct(s) {
if (!s || !s.tx_packets) return '-';
return (s.tx_retries/(s.tx_packets+s.tx_retries)*100).toFixed(1)+'%';
}
/* ── Mini Band Chip (compact for FE diagram) ── */
function renderBandChip(band, txQ, stats) {
var info = bandInfo[band] || { name: 'Band '+band, accent: '#888' };
var id = 'band-'+band;
var h = bandHealth(stats);
var type = txQ ? txQ.type : '?';
var rp = retryPct(stats);
return E('div', { 'id': id, 'style': 'background:var(--soc-card-bg);border:1px solid var(--soc-border);border-left:2px solid '+info.accent+';border-radius:6px;padding:10px 12px' }, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;margin-bottom:6px' }, [
E('span', { 'class': 'soc-text', 'style': 'font-size:13px;font-weight:bold' }, info.name),
E('span', { 'style': 'background:'+(type==='npu'?'#1565c0':'#666')+';color:#fff;padding:1px 6px;border-radius:3px;font-size:9px;font-weight:600' }, type.toUpperCase())
]),
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;font-size:12px' }, [
E('div', { 'id': id+'-health', 'style': 'display:flex;align-items:center;gap:4px' }, [
E('span', { 'style': 'width:7px;height:7px;border-radius:50%;background:'+h.color+';display:inline-block' }),
E('span', { 'style': 'color:'+h.color+';font-weight:500' }, h.text)
]),
E('span', { 'id': id+'-clients', 'class': 'soc-muted' }, stats.count + ' sta'),
(stats.tx_packets > 0) ? E('span', { 'id': id+'-retries', 'class': 'soc-muted' }, rp) : E('span')
])
]);
}
function updateBandChip(band, stats) {
var id = 'band-'+band, h = bandHealth(stats);
var el = document.getElementById(id+'-health');
if (el) { el.innerHTML = ''; el.appendChild(E('span',{'style':'width:6px;height:6px;border-radius:50%;background:'+h.color+';display:inline-block'})); el.appendChild(E('span',{'style':'color:'+h.color+';font-weight:500;font-size:11px'},h.text)); }
var cl = document.getElementById(id+'-clients');
if (cl) cl.textContent = stats.count+'sta';
var re = document.getElementById(id+'-retries');
if (re) { var rp2 = retryPct(stats); re.textContent = rp2; }
}
/* ── Frame Engine Diagram (with WiFi bands, NPU, PPE flows) ── */
function renderFeDiagram(fe, ti, st) {
if (!fe || fe.error) return E('div', { 'class': 'soc-muted' }, 'devmem not available on this build');
ti = ti || {}; st = st || {};
var ports = Array.isArray(fe.pse_ports) ? fe.pse_ports : [];
// Helper: GDM card
function gdmCard(key, name, label, color, pse) {
var d = fe[key] || {};
var active = d.tx > 0 || d.rx > 0;
return E('div', { 'class': 'soc-card soc-card-accent', 'style': 'border-left-color:'+color + (active?';border-color:'+color:'') }, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:baseline;margin-bottom:2px' }, [
E('span', { 'style': 'font-weight:bold;color:'+color+';font-size:14px' }, name),
E('span', { 'class': 'soc-label' }, pse)
]),
E('div', { 'class': 'soc-label', 'style': 'margin-bottom:6px' }, label),
E('div', { 'style': 'display:grid;grid-template-columns:auto 1fr;gap:2px 10px;font-size:12px' }, [
E('span', { 'class': 'soc-muted' }, 'TX'), E('span', { 'class': 'soc-text', 'style': 'text-align:right' }, fmtK(d.tx)),
E('span', { 'class': 'soc-muted' }, 'RX'), E('span', { 'class': 'soc-text', 'style': 'text-align:right' }, fmtK(d.rx))
].concat(d.tx_drop > 0 ? [
E('span', { 'style': 'color:#f44336' }, 'TX Drop'), E('span', { 'style': 'color:#f44336;text-align:right' }, fmtK(d.tx_drop))
] : []).concat(d.rx_drop > 0 ? [
E('span', { 'style': 'color:#f44336' }, 'RX Drop'), E('span', { 'style': 'color:#f44336;text-align:right' }, fmtK(d.rx_drop))
] : []))
]);
}
// Helper: CDM offload bar
function cdmCard(key, name, label, pse) {
var d = fe[key] || {};
var total = (d.rx_cpu||0) + (d.rx_hwf||0);
var pct = total > 0 ? ((d.rx_hwf/total)*100).toFixed(1) : '0.0';
var barCol = total===0 ? 'var(--soc-border)' : parseFloat(pct)>80 ? '#4caf50' : parseFloat(pct)>50 ? '#ff9800' : '#f44336';
return E('div', { 'class': 'soc-card' }, [
E('div', { 'style': 'display:flex;justify-content:space-between;margin-bottom:4px' }, [
E('span', { 'style': 'font-weight:bold;color:#607d8b;font-size:13px' }, name+' '+pse),
E('span', { 'class': 'soc-label' }, label)
]),
E('div', { 'class': 'soc-text', 'style': 'font-size:12px;margin-bottom:4px' }, 'HW Offload: '+pct+'%'),
E('div', { 'class': 'soc-bar-track', 'style': 'height:6px' }, [
E('div', { 'style': 'background:'+barCol+';height:100%;width:'+pct+'%;transition:width .5s;border-radius:4px' })
]),
E('div', { 'style': 'display:flex;justify-content:space-between;font-size:11px;margin-top:4px' }, [
E('span', { 'class': 'soc-muted' }, 'CPU: '+fmtK(d.rx_cpu||0)),
E('span', { 'class': 'soc-muted' }, 'HWF: '+fmtK(d.rx_hwf||0)),
E('span', { 'class': 'soc-muted' }, 'TX: '+fmtK(d.tx||0))
])
]);
}
// WiFi band chips for CDM4
var bandChips = [];
for (var b = 0; b < 3; b++) bandChips.push(renderBandChip(b, getTxQueue(ti, b), getBandStats(ti, b)));
// CDM4/WDMA + WiFi bands grouped
var p7 = ports[7] || { iq: 0, oq: 0, drops: 0 };
var cdm4WiFi = E('div', { 'class': 'soc-card soc-card-accent', 'style': 'border-left-color:#9c27b0' }, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:baseline;margin-bottom:2px' }, [
E('span', { 'style': 'font-weight:bold;color:#9c27b0;font-size:14px' }, 'CDM4 / WDMA'),
E('span', { 'class': 'soc-label' }, 'P7 WiFi DMA')
]),
E('div', { 'style': 'display:flex;gap:12px;font-size:11px;margin-bottom:8px' }, [
E('span', { 'class': 'soc-muted' }, 'IQ '+p7.iq),
E('span', { 'class': 'soc-muted' }, 'OQ '+p7.oq),
p7.drops > 0 ? E('span', { 'style': 'color:#f44336' }, 'Drop '+fmtK(p7.drops)) : null
].filter(Boolean)),
// WiFi bands inside
E('div', { 'style': 'display:grid;grid-template-columns:repeat(3,1fr);gap:6px' }, bandChips)
]);
// NPU indicator
var npuActive = st.npu_loaded;
var npuCard = E('div', { 'class': 'soc-card', 'style': 'border-color:'+(npuActive?'#00bcd4':'var(--soc-border)') }, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;margin-bottom:4px' }, [
E('span', { 'style': 'font-weight:bold;color:#00bcd4;font-size:14px' }, 'NPU'),
E('span', { 'style': 'background:'+(npuActive?'#00695c':'#666')+';color:#fff;padding:1px 7px;border-radius:3px;font-size:10px;font-weight:600' }, npuActive ? 'ACTIVE' : 'OFF')
]),
E('div', { 'class': 'soc-label', 'style': 'margin-bottom:4px' }, '8x RISC-V via PCIe RAM'),
E('div', { 'style': 'font-size:11px' }, [
E('span', { 'class': 'soc-muted' }, 'Manages: '),
E('span', { 'class': 'soc-text', 'style': 'font-size:11px' }, 'PPE init, WDMA rings, flow stats')
])
]);
// PPE engines with flow count
var ppeCard = E('div', { 'class': 'soc-card', 'style': 'border-color:#2196f3' }, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;margin-bottom:4px' }, [
E('span', { 'style': 'font-weight:bold;color:#2196f3;font-size:14px' }, 'PPE Engines'),
E('span', { 'class': 'soc-label' }, 'P4 + P8')
]),
E('div', { 'style': 'display:flex;gap:16px;font-size:12px' }, [
E('span', {}, [
E('span', { 'class': 'soc-muted' }, 'Bound '),
E('span', { 'class': 'soc-text', 'style': 'font-weight:bold', 'id': 'fe-ppe-bound' }, (st.offload_bound||0).toString())
]),
E('span', {}, [
E('span', { 'class': 'soc-muted' }, 'Total '),
E('span', { 'class': 'soc-text', 'id': 'fe-ppe-total' }, (st.offload_total||0).toString())
])
])
]);
// PSE buffer
var pseT = (fe.pse_used||0)+(fe.pse_free||0);
var pseP = pseT>0 ? ((fe.pse_used/pseT)*100).toFixed(1) : '0';
var pseCol = parseFloat(pseP)>80?'#f44336':parseFloat(pseP)>50?'#ff9800':'#4caf50';
// PSE port cells (skip P7 since it's shown in CDM4/WiFi section)
var portCells = ports.filter(function(p){ return p.port !== 7; }).map(function(p) {
var info = psePortMap[p.port] || { name:'P'+p.port, label:'?', color:'#666' };
var drop = p.drops > 0;
return E('div', { 'class': 'soc-pse-cell', 'style': drop ? 'border-color:#f44336' : '' }, [
E('div', { 'style': 'font-weight:600;color:'+info.color+';font-size:11px' }, 'P'+p.port+' '+info.name),
E('div', { 'style': 'display:flex;gap:8px;font-size:11px;margin-top:2px' }, [
E('span', { 'class': 'soc-muted' }, 'IQ '+p.iq),
E('span', { 'class': 'soc-muted' }, 'OQ '+p.oq),
drop ? E('span', { 'style': 'color:#f44336' }, fmtK(p.drops)) : null
].filter(Boolean))
]);
});
return E('div', { 'id': 'fe-diagram' }, [
// PSE buffer bar
E('div', { 'class': 'soc-card', 'style': 'margin-bottom:10px' }, [
E('div', { 'style': 'display:flex;justify-content:space-between;margin-bottom:4px' }, [
E('span', { 'class': 'soc-text', 'style': 'font-weight:bold;font-size:13px' }, 'PSE Shared Buffer'),
E('span', { 'class': 'soc-muted', 'style': 'font-size:12px' }, (fe.pse_used||0)+' used / '+(fe.pse_free||0)+' free ('+pseP+'%)')
]),
E('div', { 'class': 'soc-bar-track', 'style': 'height:8px' }, [
E('div', { 'style': 'background:'+pseCol+';height:100%;width:'+pseP+'%;border-radius:4px;transition:width .5s' })
])
]),
// Row 1: GDM ports
E('div', { 'class': 'soc-gdm-grid' }, [
gdmCard('gdm1', 'GDM1', 'Internal Switch (1G LAN3/4)', '#ff9800', 'P1'),
gdmCard('gdm2', 'GDM2', 'WAN (USXGMII 10G)', '#4caf50', 'P2'),
gdmCard('gdm4', 'GDM4', 'LAN2 (USXGMII 10G)', '#4caf50', 'P9')
]),
// Row 2: CDM1/CDM2 (CPU) + CDM4/WiFi
E('div', { 'style': 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:10px' }, [
cdmCard('cdm1', 'CDM1', 'CPU DMA 1', 'P0'),
cdmCard('cdm2', 'CDM2', 'CPU DMA 2', 'P5'),
cdm4WiFi
]),
// Row 3: PPE + NPU
E('div', { 'style': 'display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px' }, [
ppeCard,
npuCard
]),
// PSE port grid
E('div', { 'class': 'soc-text', 'style': 'font-size:12px;font-weight:600;margin-bottom:6px' }, 'PSE Port Queue Status'),
E('div', { 'class': 'soc-pse-grid' }, portCells)
]);
}
/* ── CPU Frequency ── */
function freqBarState(hw, min, max, pll, gov) {
var oc = gov==='performance' && pll>0 && (pll*1000)>max;
return { freq: oc ? pll*1000 : Math.min(hw,max), max: oc ? pll*1000 : max, oc: oc };
}
function renderFreqBar(hw, min, max, pll, gov) {
if (!max) return E('span',{},'N/A');
var s = freqBarState(hw,min,max,pll,gov);
var pct = Math.round(((s.freq-min)/(s.max-min))*100);
pct = Math.max(0,Math.min(100,pct));
var bg = s.oc ? 'linear-gradient(90deg,#e65100,#ff9800)' : 'linear-gradient(90deg,#2e7d32,#66bb6a)';
var label = s.oc ? (pll+' MHz (OC)') : fmtFreq(s.freq);
return E('div', { 'id':'cpu-freq-bar-wrap', 'style':'display:flex;align-items:center;gap:10px' }, [
E('span', { 'class':'soc-muted', 'style':'font-size:90%' }, fmtFreq(min)),
E('div', { 'style':'flex:1;border-radius:4px;height:22px;position:relative;min-width:180px;max-width:350px;overflow:hidden', 'class':'soc-bar-track' }, [
E('div', { 'id':'cpu-freq-fill', 'style':'background:'+bg+';height:100%;border-radius:4px;width:'+pct+'%;transition:width .5s' }),
E('span', { 'id':'cpu-freq-text', 'style':'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-weight:bold;font-size:13px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6)' }, label)
]),
E('span', { 'id':'cpu-freq-max-label', 'class':'soc-muted', 'style':'font-size:90%' }, fmtFreq(s.max))
]);
}
function updateFreqBar(hw, min, max, pll, gov) {
var s = freqBarState(hw,min,max,pll,gov);
var el = document.getElementById('cpu-freq-text'), fl = document.getElementById('cpu-freq-fill'), ml = document.getElementById('cpu-freq-max-label');
if (el) el.textContent = s.oc ? (pll+' MHz (OC)') : fmtFreq(s.freq);
if (fl && s.max>0) { var pct=Math.max(0,Math.min(100,Math.round(((s.freq-min)/(s.max-min))*100))); fl.style.width=pct+'%'; fl.style.background=s.oc?'linear-gradient(90deg,#e65100,#ff9800)':'linear-gradient(90deg,#2e7d32,#66bb6a)'; }
if (ml) ml.textContent = fmtFreq(s.max);
}
function renderGovSelect(avail, active) {
var gs = (avail||'').trim().split(/\s+/).filter(Boolean);
if (!gs.length) return E('span',{},'N/A');
return E('select', { 'id':'cpu-governor-select','class':'cbi-input-select','style':'min-width:140px','change':function(ev){
var g=ev.target.value; ev.target.disabled=true;
callSetGovernor(g).then(function(r){ev.target.disabled=false;if(r&&r.error) ui.addNotification(null,E('p',{},_('Error: ')+r.error),'error');}).catch(function(){ev.target.disabled=false;});
}}, gs.map(function(g){return E('option',{'value':g,'selected':g===active?'':null},g);}));
}
function renderMaxFreqSelect(avail, cur) {
var fs = (avail||'').trim().split(/\s+/).filter(Boolean);
if (!fs.length) return E('span',{},'N/A');
return E('select', { 'id':'cpu-maxfreq-select','class':'cbi-input-select','style':'min-width:140px','change':function(ev){
var f=ev.target.value; ev.target.disabled=true;
callSetMaxFreq(parseInt(f)).then(function(r){ev.target.disabled=false;if(r&&r.error) ui.addNotification(null,E('p',{},_('Error: ')+r.error),'error');}).catch(function(){ev.target.disabled=false;});
}}, fs.map(function(f){return E('option',{'value':f,'selected':parseInt(f)===parseInt(cur)?'':null},(parseInt(f)/1000).toFixed(0)+' MHz');}));
}
function renderOcControls() {
var inp = E('input',{'id':'oc-freq-input','type':'number','min':'500','max':'1600','step':'50','value':'1400','class':'cbi-input-text','style':'width:100px'});
var btn = E('button',{'class':'cbi-button cbi-button-action','style':'margin-left:8px','click':function(){
var f=parseInt(document.getElementById('oc-freq-input').value);
if(isNaN(f)||f<500||f>1600){ui.addNotification(null,E('p',{},_('Must be 500-1600 MHz')),'error');return;}
if(f>1400&&!confirm('Frequencies above 1400 MHz may be unstable. Continue?')) return;
btn.disabled=true;btn.textContent=_('Applying...');
callSetOverclock(f).then(function(r){btn.disabled=false;btn.textContent=_('Apply');
if(r&&r.error) ui.addNotification(null,E('p',{},_('Failed: ')+r.error),'error');
else if(r&&r.result==='ok') ui.addNotification(null,E('p',{},_('CPU set to ')+r.actual_mhz+' MHz'),'info');
}).catch(function(e){btn.disabled=false;btn.textContent=_('Apply');});
}},_('Apply'));
return E('div',{'style':'display:flex;align-items:center;gap:8px;flex-wrap:wrap'},[
inp, E('span',{'class':'soc-muted'},'MHz'), btn,
E('span',{'class':'soc-muted','style':'font-size:85%;margin-left:8px'},_('Direct PLL. Stock max 1200 MHz. Stable up to 1500 MHz.'))
]);
}
/* ── PPE Table ── */
function renderPpeRows(entries) {
return entries.slice(0,100).map(function(e) {
var eth = e.eth||''; if(eth==='00:00:00:00:00:00->00:00:00:00:00:00') eth='-';
return E('tr',{'class':'tr'},[
E('td',{'class':'td'},e.index), E('td',{'class':'td'},E('span',{'class':e.state==='BND'?'label-success':''},e.state)),
E('td',{'class':'td'},e.type), E('td',{'class':'td'},e.orig||'-'), E('td',{'class':'td'},e.new_flow||'-'), E('td',{'class':'td'},eth)
]);
});
}
/* ── Main View ── */
return view.extend({
load: function() {
return Promise.all([ callNpuStatus(), callPpeEntries(), callTokenInfo(), callFrameEngine() ]);
},
render: function(data) {
injectCSS();
var st = data[0]||{}, ppe = data[1]||{}, ti = data[2]||{}, fe = data[3]||{};
var entries = Array.isArray(ppe.entries) ? ppe.entries : [];
var memR = Array.isArray(st.memory_regions) ? st.memory_regions : [];
var view = E('div',{'class':'cbi-map'},[
E('h2',{},_('Airoha SoC Status')),
// CPU Frequency
E('div',{'class':'cbi-section'},[
E('h3',{},_('CPU Frequency')),
E('table',{'class':'table'},[
E('tr',{'class':'tr'},[ E('td',{'class':'td','width':'33%'},E('strong',{},_('Current Frequency'))), E('td',{'class':'td'}, renderFreqBar(st.cpu_hw_freq,st.cpu_min_freq,st.cpu_max_freq,st.pll_freq_mhz,st.cpu_governor)) ]),
E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('Governor'))), E('td',{'class':'td'}, renderGovSelect(st.cpu_avail_governors,st.cpu_governor)) ]),
E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('Max Frequency'))), E('td',{'class':'td'}, renderMaxFreqSelect(st.cpu_avail_freqs,st.cpu_max_freq)) ]),
E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('Overclock'))), E('td',{'class':'td'}, renderOcControls()) ]),
E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('CPU Cores'))), E('td',{'class':'td'},(st.cpu_count||0).toString()) ])
])
]),
// NPU & Frame Engine (unified)
E('div',{'class':'cbi-section'},[
E('h3',{},_('NPU & Offload Engine')),
E('table',{'class':'table'},[
E('tr',{'class':'tr'},[ E('td',{'class':'td','width':'33%'},E('strong',{},_('NPU Status'))),
E('td',{'class':'td','id':'npu-status'}, st.npu_loaded ?
E('span',{'class':'label-success'},_('Active')+(st.npu_device?' ('+st.npu_device+')':'')) :
E('span',{'class':'label-danger'},_('Not Active'))) ]),
E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('Firmware / Clock / Cores'))),
E('td',{'class':'td','id':'npu-info'}, (st.npu_version||'N/A')+' | '+(st.npu_clock?(st.npu_clock/1e6).toFixed(0)+' MHz':'N/A')+' | '+(st.npu_cores||0)+' cores') ]),
E('tr',{'class':'tr'},[ E('td',{'class':'td'},E('strong',{},_('Reserved Memory'))),
E('td',{'class':'td','id':'npu-memory'}, calcTotalMem(memR)+' ('+memR.length+' regions)') ])
]),
// Frame Engine diagram (includes WiFi bands, PPE flows, NPU indicator)
E('div',{'style':'margin-top:12px'},[ E('h4',{'class':'soc-text','style':'font-size:14px;margin-bottom:8px'},_('Frame Engine'))]),
E('div',{'id':'fe-container'}, renderFeDiagram(fe, ti, st))
]),
// PPE Flow Table
E('div',{'class':'cbi-section'},[
E('h3',{},_('PPE Flow Offload Entries')),
E('table',{'class':'table','id':'ppe-entries-table'},[
E('tr',{'class':'tr cbi-section-table-titles'},[
E('th',{'class':'th'},_('Index')), E('th',{'class':'th'},_('State')), E('th',{'class':'th'},_('Type')),
E('th',{'class':'th'},_('Original Flow')), E('th',{'class':'th'},_('New Flow')), E('th',{'class':'th'},_('Ethernet'))
])
].concat(renderPpeRows(entries)))
])
]);
poll.add(L.bind(function() {
return Promise.all([ callNpuStatus(), callPpeEntries(), callTokenInfo(), callFrameEngine() ]).then(L.bind(function(d) {
injectCSS();
var st=d[0]||{}, ppe=d[1]||{}, ti=d[2]||{}, fe=d[3]||{};
var entries = Array.isArray(ppe.entries)?ppe.entries:[];
updateFreqBar(st.cpu_hw_freq,st.cpu_min_freq,st.cpu_max_freq,st.pll_freq_mhz,st.cpu_governor);
var gs=document.getElementById('cpu-governor-select'); if(gs&&!gs.matches(':focus')) gs.value=st.cpu_governor||'';
var fs=document.getElementById('cpu-maxfreq-select'); if(fs&&!fs.matches(':focus')) fs.value=(st.cpu_max_freq||0).toString();
var se=document.getElementById('npu-status');
if(se){se.innerHTML='';var sp=document.createElement('span');sp.className=st.npu_loaded?'label-success':'label-danger';sp.textContent=st.npu_loaded?(_('Active')+(st.npu_device?' ('+st.npu_device+')':'')):_('Not Active');se.appendChild(sp);}
var fc=document.getElementById('fe-container'); if(fc){fc.innerHTML='';fc.appendChild(renderFeDiagram(fe, ti, st));}
var tb=document.getElementById('ppe-entries-table');
if(tb){while(tb.rows.length>1)tb.deleteRow(1);renderPpeRows(entries).forEach(function(r){tb.appendChild(r);});}
},this));
},this), 5);
return view;
},
handleSaveApply: null, handleSave: null, handleReset: null
});

View File

@ -0,0 +1,176 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.8\n"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:327
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:406
msgid "Active"
msgstr "Activo"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:284
msgid "Airoha SoC Status"
msgstr "Estado del SoC Airoha"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:224
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:233
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:237
msgid "Apply"
msgstr "Aplicar"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:221
msgid "Applying..."
msgstr "Aplicando..."
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:350
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:420
msgid "Bound:"
msgstr "Atado:"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:362
msgid "Bytes"
msgstr "Bytes"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:310
msgid "CPU Cores"
msgstr "Núcleos de CPU"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:288
msgid "CPU Frequency"
msgstr "Frecuencia de la CPU"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:229
msgid "CPU set to"
msgstr "CPU configurada en"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:291
msgid "Current Frequency"
msgstr "Frecuencia actual"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:244
msgid ""
"Direct PLL programming. Governor locked to performance. Stock max: 1200 MHz. "
"Tested stable up to 1500 MHz."
msgstr ""
"Programación directa por PLL. Regulador ajustado al rendimiento. Máxima de "
"fábrica: 1200 MHz. Estabilidad comprobada hasta 1500 MHz."
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:360
msgid "Ethernet"
msgstr "Ethernet"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:85
msgid "Failed to set governor:"
msgstr "No se pudo establecer el regulador:"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:112
msgid "Failed to set max frequency:"
msgstr "No se pudo establecer la frecuencia máxima:"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:212
msgid "Frequency must be 500-1600 MHz"
msgstr "La frecuencia debe ser de 500-1600 MHz"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:296
msgid "Governor"
msgstr "Regulador"
#: feeds/luci/applications/luci-app-airoha-npu/root/usr/share/rpcd/acl.d/luci-app-airoha-npu.json:3
msgid "Grant access to Airoha SoC status (NPU + CPU + Overclock)"
msgstr "Otorgar acceso al estado del SoC Airoha (NPU + CPU + Overclock)"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:355
msgid "Index"
msgstr "Índice"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:301
msgid "Max Frequency"
msgstr "Máx. frecuencia"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:331
msgid "NPU Clock / Cores"
msgstr "Reloj/núcleos de NPU"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:321
msgid "NPU Firmware Version"
msgstr "Versión de firmware de la NPU"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:318
msgid "NPU Information"
msgstr "Información de la NPU"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:325
msgid "NPU Status"
msgstr "Estado de la NPU"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:359
msgid "New Flow"
msgstr "Nuevo flujo"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:328
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:411
msgid "Not Active"
msgstr "No activo"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:322
msgid "Not available"
msgstr "No disponible"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:339
msgid "Offload Statistics"
msgstr "Estadísticas de descarga"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:358
msgid "Original Flow"
msgstr "Flujo original"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:306
msgid "Overclock"
msgstr "Overclock"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:226
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:234
msgid "Overclock failed:"
msgstr "Overclock fallido:"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:347
msgid "PPE Flow Offload Entries"
msgstr "Entradas de descarga de flujo de PPE"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:361
msgid "Packets"
msgstr "Packets"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:335
msgid "Reserved Memory"
msgstr "Memoria reservada"
#: feeds/luci/applications/luci-app-airoha-npu/root/usr/share/luci/menu.d/luci-app-airoha-npu.json:3
msgid "SoC Status"
msgstr "Procesadores"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:356
msgid "State"
msgstr "Estado"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:349
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:419
msgid "Total:"
msgstr "Total:"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:357
msgid "Type"
msgstr "Tipo"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:351
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:421
msgid "Unbound:"
msgstr "Desatado:"

View File

@ -0,0 +1,164 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:327
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:406
msgid "Active"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:284
msgid "Airoha SoC Status"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:224
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:233
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:237
msgid "Apply"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:221
msgid "Applying..."
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:350
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:420
msgid "Bound:"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:362
msgid "Bytes"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:310
msgid "CPU Cores"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:288
msgid "CPU Frequency"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:229
msgid "CPU set to"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:291
msgid "Current Frequency"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:244
msgid ""
"Direct PLL programming. Governor locked to performance. Stock max: 1200 MHz. "
"Tested stable up to 1500 MHz."
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:360
msgid "Ethernet"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:85
msgid "Failed to set governor:"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:112
msgid "Failed to set max frequency:"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:212
msgid "Frequency must be 500-1600 MHz"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:296
msgid "Governor"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/root/usr/share/rpcd/acl.d/luci-app-airoha-npu.json:3
msgid "Grant access to Airoha SoC status (NPU + CPU + Overclock)"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:355
msgid "Index"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:301
msgid "Max Frequency"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:331
msgid "NPU Clock / Cores"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:321
msgid "NPU Firmware Version"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:318
msgid "NPU Information"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:325
msgid "NPU Status"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:359
msgid "New Flow"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:328
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:411
msgid "Not Active"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:322
msgid "Not available"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:339
msgid "Offload Statistics"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:358
msgid "Original Flow"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:306
msgid "Overclock"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:226
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:234
msgid "Overclock failed:"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:347
msgid "PPE Flow Offload Entries"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:361
msgid "Packets"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:335
msgid "Reserved Memory"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/root/usr/share/luci/menu.d/luci-app-airoha-npu.json:3
msgid "SoC Status"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:356
msgid "State"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:349
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:419
msgid "Total:"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:357
msgid "Type"
msgstr ""
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:351
#: feeds/luci/applications/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js:421
msgid "Unbound:"
msgstr ""

View File

@ -0,0 +1,483 @@
#!/bin/sh
# Airoha SoC Status RPC backend for LuCI (NPU + CPU + Overclock)
. /lib/functions.sh
get_status() {
# === NPU Status ===
# Read the actually-loaded NPU firmware path from DTS (firmware-name property).
local npu_fw=""
local npu_dt="/proc/device-tree/soc/npu@1e900000/firmware-name"
if [ -f "$npu_dt" ]; then
# firmware-name is a string list (null-separated). First entry is the rv32 code.
local fw_rel=$(tr '\0' '\n' < "$npu_dt" 2>/dev/null | grep -m1 "rv32")
[ -n "$fw_rel" ] && npu_fw="/lib/firmware/$fw_rel"
fi
local npu_ver="Unknown"
if [ -n "$npu_fw" ] && [ -f "$npu_fw" ]; then
npu_ver=$(strings "$npu_fw" 2>/dev/null | grep -oE '([0-9]+\.[0-9]+\.[0-9]+-)?TLB[0-9.]+[-_v0-9]*' | head -1)
[ -z "$npu_ver" ] && npu_ver="Unknown"
fi
local npu_loaded="false"
local npu_device=""
local npu_driver_path="/sys/bus/platform/drivers/airoha-npu"
if [ -d "$npu_driver_path" ]; then
npu_device=$(ls -1 "$npu_driver_path" 2>/dev/null | grep '\.npu$' | head -1)
if [ -n "$npu_device" ]; then
npu_loaded="true"
fi
fi
local npu_clock=0
if [ -f "/sys/kernel/debug/clk/npu/clk_rate" ]; then
npu_clock=$(cat /sys/kernel/debug/clk/npu/clk_rate 2>/dev/null || echo 0)
fi
local npu_cores=$(grep -c "airoha-npu-wdt" /proc/interrupts 2>/dev/null || echo 0)
local offload_bound=$(cat /sys/kernel/debug/ppe/bind 2>/dev/null | grep -c "^[0-9a-fA-F]")
local offload_total=$(cat /sys/kernel/debug/ppe/entries 2>/dev/null | grep -c "^[0-9a-fA-F]")
local mem_info=$(dmesg | grep "reserved mem.*npu" | head -4)
local regions="[]"
if [ -n "$mem_info" ]; then
regions=$(echo "$mem_info" | awk '
BEGIN { printf "[" }
{
if (NR > 1) printf ","
match($0, /0x[0-9a-fA-F]+\.\.0x[0-9a-fA-F]+/)
range = substr($0, RSTART, RLENGTH)
split(range, a, "\\.\\.")
match($0, /\([0-9]+ [KMG]i?B\)/)
size = substr($0, RSTART+1, RLENGTH-2)
match($0, /non-reusable [^ ]+/)
name = substr($0, RSTART+13)
gsub(/@.*/, "", name)
printf "{\"name\":\"%s\",\"start\":\"%s\",\"end\":\"%s\",\"size\":\"%s\"}", name, a[1], a[2], size
}
END { printf "]" }
')
fi
# === CPU Frequency Status ===
local cpufreq_path="/sys/devices/system/cpu/cpufreq/policy0"
local cpu_cur_freq=0
local cpu_hw_freq=0
local cpu_governor="unknown"
local cpu_avail_governors=""
local cpu_min_freq=0
local cpu_max_freq=0
local cpu_avail_freqs=""
if [ -d "$cpufreq_path" ]; then
cpu_cur_freq=$(cat "$cpufreq_path/scaling_cur_freq" 2>/dev/null || echo 0)
cpu_hw_freq=$(cat "$cpufreq_path/cpuinfo_cur_freq" 2>/dev/null || echo 0)
cpu_governor=$(cat "$cpufreq_path/scaling_governor" 2>/dev/null || echo "unknown")
cpu_avail_governors=$(cat "$cpufreq_path/scaling_available_governors" 2>/dev/null || echo "")
cpu_min_freq=$(cat "$cpufreq_path/scaling_min_freq" 2>/dev/null || echo 0)
cpu_max_freq=$(cat "$cpufreq_path/scaling_max_freq" 2>/dev/null || echo 0)
cpu_avail_freqs=$(cat "$cpufreq_path/scaling_available_frequencies" 2>/dev/null || echo "")
fi
# Read actual PLL frequency from hardware (detects overclock)
local pll_freq=0
if command -v devmem > /dev/null 2>&1; then
local pcw_raw=$(devmem 0x1fa202b4 2>/dev/null)
local chg_raw=$(devmem 0x1fa202b8 2>/dev/null)
if [ -n "$pcw_raw" ] && [ -n "$chg_raw" ]; then
pcw_raw=$((pcw_raw))
chg_raw=$((chg_raw))
local pcw_int=$(( (pcw_raw >> 24) & 0x7F ))
local pll_posdiv=$(( (chg_raw >> 4) & 7 ))
if [ "$pll_posdiv" -eq 0 ]; then
pll_freq=$((pcw_int * 50))
else
pll_freq=$((pcw_int * 50 / (1 << pll_posdiv)))
fi
fi
fi
# Count online CPUs
local cpu_count=0
if [ -d "/sys/devices/system/cpu" ]; then
cpu_count=$(ls -d /sys/devices/system/cpu/cpu[0-9]* 2>/dev/null | wc -l)
fi
printf '{"npu_version":"%s","npu_loaded":%s,"npu_device":"%s","npu_clock":%d,"npu_cores":%d,"offload_bound":%d,"offload_total":%d,"memory_regions":%s,"cpu_cur_freq":%d,"cpu_hw_freq":%d,"cpu_governor":"%s","cpu_avail_governors":"%s","cpu_min_freq":%d,"cpu_max_freq":%d,"cpu_avail_freqs":"%s","cpu_count":%d,"pll_freq_mhz":%d}' \
"${npu_ver:-Unknown}" "$npu_loaded" "${npu_device:-}" "${npu_clock:-0}" "${npu_cores:-0}" "${offload_bound:-0}" "${offload_total:-0}" "$regions" \
"${cpu_cur_freq:-0}" "${cpu_hw_freq:-0}" "${cpu_governor:-unknown}" "${cpu_avail_governors:-}" "${cpu_min_freq:-0}" "${cpu_max_freq:-0}" "${cpu_avail_freqs:-}" "${cpu_count:-0}" "${pll_freq:-0}"
}
get_ppe_entries() {
local entries_file="/sys/kernel/debug/ppe/entries"
if [ ! -f "$entries_file" ]; then
echo '{"entries":[]}'
return
fi
awk '
BEGIN {
printf "{\"entries\":["
first = 1
}
/^[0-9a-fA-F]+ / {
if (first == 0) printf ","
first = 0
index_val = $1
state = $2
type = ""
subtype = ""
if ($3 == "IPv4" || $3 == "IPv6") {
type = $3
subtype = $4
} else if ($3 == "L2B" || $4 == "L2B") {
type = "L2B"
subtype = ""
} else {
type = $3
subtype = $4
}
orig = ""
match($0, /orig=[^ ]+/)
if (RSTART > 0) {
orig = substr($0, RSTART+5, RLENGTH-5)
}
new_flow = ""
match($0, /new=[^ ]+/)
if (RSTART > 0) {
new_flow = substr($0, RSTART+4, RLENGTH-4)
}
eth = ""
match($0, /eth=[^ ]+/)
if (RSTART > 0) {
eth = substr($0, RSTART+4, RLENGTH-4)
}
printf "{\"index\":\"%s\",\"state\":\"%s\",\"type\":\"%s %s\",\"orig\":\"%s\",\"new_flow\":\"%s\",\"eth\":\"%s\"}", \
index_val, state, type, subtype, orig, new_flow, eth
}
END {
printf "]}"
}
' "$entries_file" 2>/dev/null || echo '{"entries":[]}'
}
get_token_info() {
local token_file="/sys/kernel/debug/ieee80211/phy0/mt76/token_info"
# Gather per-band WiFi station stats (count, retries, failures)
local sta_json="" band
for band in 0 1 2; do
local stats=$(
for iface in $(iw dev 2>/dev/null | awk '/Interface phy0\.'$band'-/{print $2}'); do
iw dev "$iface" station dump 2>/dev/null
done | awk '
BEGIN { n=0; tp=0; tr=0 }
/^Station/ { n++ }
/tx packets:/ { tp += $3 }
/tx retries:/ { tr += $3 }
END { printf "%d %d %d", n, tp, tr }'
)
set -- $stats
[ -n "$sta_json" ] && sta_json="${sta_json},"
sta_json="${sta_json}{\"band\":${band},\"count\":${1:-0},\"tx_packets\":${2:-0},\"tx_retries\":${3:-0}}"
done
if [ ! -f "$token_file" ]; then
printf '{"token_count":0,"token_size":0,"npu_active":false,"tx_queues":[],"station_counts":[%s]}' "$sta_json"
return
fi
awk -v stations="$sta_json" '
BEGIN {
tc=0; ts=0; npu=0
tq_n=0
}
/^ token_count:/ { tc=$2+0; ts=$4+0 }
/status:.*active/ { npu=1 }
/band [0-9]/ {
gsub(/\|/, " ")
for(i=1;i<=NF;i++) {
if($i=="band") tq_band[tq_n]=$(i+1)+0
if($i=="npu" || $i=="dma") tq_type[tq_n]=$i
}
cnt=0
for(i=1;i<=NF;i++) {
if($i ~ /^[0-9]+$/ && $(i-1)!="band") {
tq_v[tq_n,cnt++]=$i+0
}
}
tq_n++
}
END {
printf "{\"token_count\":%d,\"token_size\":%d,\"npu_active\":%s,\"tx_queues\":[", \
tc, ts, (npu?"true":"false")
for(i=0;i<tq_n;i++) {
if(i>0) printf ","
printf "{\"band\":%d,\"type\":\"%s\",\"ndesc\":%d,\"queued\":%d,\"head\":%d,\"tail\":%d}", \
tq_band[i], tq_type[i], tq_v[i,0], tq_v[i,1], tq_v[i,2], tq_v[i,3]
}
printf "],\"station_counts\":[%s]}", stations
}
' "$token_file" 2>/dev/null || printf '{"token_count":0,"token_size":0,"npu_active":false,"tx_queues":[],"station_counts":[%s]}' "$sta_json"
}
set_governor() {
local governor
governor=$(echo "$1" | jsonfilter -e "@.governor" 2>/dev/null)
if [ -z "$governor" ]; then
echo '{"error":"missing governor parameter"}'
return
fi
# Validate governor is available
local avail=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_governors 2>/dev/null)
if ! echo "$avail" | grep -qw "$governor"; then
printf '{"error":"invalid governor: %s"}' "$governor"
return
fi
echo "$governor" > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null
if [ $? -eq 0 ]; then
printf '{"result":"ok","governor":"%s"}' "$governor"
else
echo '{"error":"failed to set governor"}'
fi
}
set_max_freq() {
local freq
freq=$(echo "$1" | jsonfilter -e "@.freq" 2>/dev/null)
if [ -z "$freq" ]; then
echo '{"error":"missing freq parameter"}'
return
fi
# Validate freq is in available list
local avail=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies 2>/dev/null)
if ! echo "$avail" | grep -qw "$freq"; then
printf '{"error":"invalid frequency: %s"}' "$freq"
return
fi
echo "$freq" > /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq 2>/dev/null
if [ $? -eq 0 ]; then
printf '{"result":"ok","freq":%d}' "$freq"
else
echo '{"error":"failed to set max frequency"}'
fi
}
set_overclock() {
local target_mhz
target_mhz=$(echo "$1" | jsonfilter -e "@.freq_mhz" 2>/dev/null)
if [ -z "$target_mhz" ]; then
echo '{"error":"missing freq_mhz parameter"}'
return
fi
# Validate range (500-1600, crash likely above 1500 at stock voltage)
if [ "$target_mhz" -lt 500 ] || [ "$target_mhz" -gt 1600 ]; then
echo '{"error":"frequency must be 500-1600 MHz"}'
return
fi
if ! command -v devmem > /dev/null 2>&1; then
echo '{"error":"devmem not available"}'
return
fi
# Calculate PCW and posdiv
local posdiv pcw_int
if [ "$target_mhz" -lt 1000 ]; then
posdiv=1
pcw_int=$((target_mhz / 25))
else
posdiv=0
pcw_int=$((target_mhz / 50))
fi
# Lock governor to performance to prevent cpufreq from fighting
echo performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null
# Read current register values
local cur_clkmux=$(($(devmem 0x1fa201e0)))
local cur_pcw=$(($(devmem 0x1fa202b4)))
local cur_chg=$(($(devmem 0x1fa202b8)))
# Step 1: Enable PLL2 path in CLK_MUX (set bit 2)
devmem 0x1fa201e0 32 $(printf "0x%08X" $((cur_clkmux | 0x4)))
# Step 2: MCUCFG switch to PLL2 (sel=3 in bits[10:9])
local cur_u=$(($(devmem 0x1efbe640)))
devmem 0x1efbe640 32 $(printf "0x%08X" $(((cur_u & 0xFFFFFFE0) | 0x12)))
local cur_s=$(($(devmem 0x1efbe7c0)))
devmem 0x1efbe7c0 32 $(printf "0x%08X" $(((cur_s & 0xFFFFF9FF) | (3 << 9))))
# Step 3: Unlock PLL registers
local cur_p=$(($(devmem 0x1fa20268)))
devmem 0x1fa20268 32 $(printf "0x%08X" $(((cur_p & 0xFFFFFF00) | 0x12)))
# Step 4: Write new PCW integer
devmem 0x1fa202b4 32 $(printf "0x%08X" $(((cur_pcw & 0x00FFFFFF) | (pcw_int << 24))))
# Step 5: Write posdiv and toggle CHG bit
local chg_bit=$((cur_chg & 1))
local cleared=$((cur_chg & 0xFFFFFF8E))
if [ "$chg_bit" -eq 1 ]; then
devmem 0x1fa202b8 32 $(printf "0x%08X" $((cleared | (posdiv << 4))))
else
devmem 0x1fa202b8 32 $(printf "0x%08X" $((cleared | (posdiv << 4) | 1)))
fi
# Step 6: Wait for PLL lock
sleep 0.02
# Step 7: Enable ARMPLL path in CLK_MUX (set bit 0)
local cur_clkmux2=$(($(devmem 0x1fa201e0)))
devmem 0x1fa201e0 32 $(printf "0x%08X" $((cur_clkmux2 | 0x1)))
# Step 8: MCUCFG switch back to ARMPLL
local cur_u2=$(($(devmem 0x1efbe640)))
devmem 0x1efbe640 32 $(printf "0x%08X" $(((cur_u2 & 0xFFFFFFE0) | 0x12)))
local cur_s2=$(($(devmem 0x1efbe7c0)))
devmem 0x1efbe7c0 32 $(printf "0x%08X" $(((cur_s2 & 0xFFFFF9FF) | (1 << 9))))
# Step 9: Clear PLL2 path in CLK_MUX
local cur_clkmux3=$(($(devmem 0x1fa201e0)))
devmem 0x1fa201e0 32 $(printf "0x%08X" $((cur_clkmux3 & 0xFFFFFFFB)))
# Step 10: Re-lock PLL registers
local cur_p2=$(($(devmem 0x1fa20268)))
devmem 0x1fa20268 32 $(printf "0x%08X" $((cur_p2 & 0xFFFFFF00)))
# Verify
local final_pcw=$(($(devmem 0x1fa202b4)))
local final_chg=$(($(devmem 0x1fa202b8)))
local read_pcw=$(( (final_pcw >> 24) & 0x7F ))
local read_posdiv=$(( (final_chg >> 4) & 7 ))
local read_freq
if [ "$read_posdiv" -eq 0 ]; then
read_freq=$((read_pcw * 50))
else
read_freq=$((read_pcw * 50 / (1 << read_posdiv)))
fi
printf '{"result":"ok","target_mhz":%d,"actual_mhz":%d,"pcw":%d,"posdiv":%d}' \
"$target_mhz" "$read_freq" "$read_pcw" "$read_posdiv"
}
get_frame_engine() {
if ! command -v devmem > /dev/null 2>&1; then
echo '{"error":"devmem not available"}'
return
fi
# PSE queue status (IQ=hi16, OQ=lo16) and drop counts for each port
local i reg_q reg_d pse_ports=""
for i in 0 1 2 3 4 5 6 7 8 9; do
reg_q=$(printf "0x%x" $((0x1fb50150 + i * 4)))
reg_d=$(printf "0x%x" $((0x1fb50120 + i * 4)))
local qval=$(($(devmem $reg_q)))
local dval=$(($(devmem $reg_d)))
local iq=$(( (qval >> 16) & 0xFFFF ))
local oq=$(( qval & 0xFFFF ))
[ -n "$pse_ports" ] && pse_ports="${pse_ports},"
pse_ports="${pse_ports}{\"port\":${i},\"iq\":${iq},\"oq\":${oq},\"drops\":${dval}}"
done
# PSE shared buffer
local psb=$(($(devmem 0x1fb50104)))
local pse_used=$(( (psb >> 16) & 0xFFFF ))
local pse_free=$(( psb & 0x7FFF ))
# GDM counters (TX_OK, TX_DROP, RX_OK, RX_FC_DROP, RX_OVER_DROP)
# Read as hex to handle large 32-bit counters
local gdm1_tx=$(($(devmem 0x1fb50604)))
local gdm1_txd=$(($(devmem 0x1fb50608)))
local gdm1_rx=$(($(devmem 0x1fb50648)))
local gdm1_rxd=$(($(devmem 0x1fb5064c)))
local gdm2_tx=$(($(devmem 0x1fb51604)))
local gdm2_txd=$(($(devmem 0x1fb51608)))
local gdm2_rx=$(($(devmem 0x1fb51648)))
local gdm2_rxd=$(($(devmem 0x1fb5164c)))
local gdm4_tx=$(($(devmem 0x1fb52604)))
local gdm4_txd=$(($(devmem 0x1fb52608)))
local gdm4_rx=$(($(devmem 0x1fb52648)))
local gdm4_rxd=$(($(devmem 0x1fb5264c)))
# CDM counters (TX_OK, RXCPU_OK, RXHWF_OK, RXCPU_DROP, RXHWF_DROP)
local cdm1_tx=$(($(devmem 0x1fb50580)))
local cdm1_rxcpu=$(($(devmem 0x1fb50590)))
local cdm1_rxhwf=$(($(devmem 0x1fb50594)))
local cdm1_rxcpud=$(($(devmem 0x1fb505a0)))
local cdm1_rxhwfd=$(($(devmem 0x1fb505a4)))
local cdm2_tx=$(($(devmem 0x1fb51580)))
local cdm2_rxcpu=$(($(devmem 0x1fb51590)))
local cdm2_rxhwf=$(($(devmem 0x1fb51594)))
local cdm2_rxcpud=$(($(devmem 0x1fb515a0)))
local cdm2_rxhwfd=$(($(devmem 0x1fb515a4)))
printf '{"pse_ports":[%s],"pse_used":%d,"pse_free":%d,' "$pse_ports" "$pse_used" "$pse_free"
printf '"gdm1":{"tx":%d,"tx_drop":%d,"rx":%d,"rx_drop":%d},' "$gdm1_tx" "$gdm1_txd" "$gdm1_rx" "$gdm1_rxd"
printf '"gdm2":{"tx":%d,"tx_drop":%d,"rx":%d,"rx_drop":%d},' "$gdm2_tx" "$gdm2_txd" "$gdm2_rx" "$gdm2_rxd"
printf '"gdm4":{"tx":%d,"tx_drop":%d,"rx":%d,"rx_drop":%d},' "$gdm4_tx" "$gdm4_txd" "$gdm4_rx" "$gdm4_rxd"
printf '"cdm1":{"tx":%d,"rx_cpu":%d,"rx_hwf":%d,"rx_cpu_drop":%d,"rx_hwf_drop":%d},' "$cdm1_tx" "$cdm1_rxcpu" "$cdm1_rxhwf" "$cdm1_rxcpud" "$cdm1_rxhwfd"
printf '"cdm2":{"tx":%d,"rx_cpu":%d,"rx_hwf":%d,"rx_cpu_drop":%d,"rx_hwf_drop":%d}}' "$cdm2_tx" "$cdm2_rxcpu" "$cdm2_rxhwf" "$cdm2_rxcpud" "$cdm2_rxhwfd"
}
case "$1" in
list)
echo '{"getStatus":{},"getPpeEntries":{},"getTokenInfo":{},"getFrameEngine":{},"setGovernor":{"governor":"str"},"setMaxFreq":{"freq":"int"},"setOverclock":{"freq_mhz":"int"}}'
;;
call)
case "$2" in
getStatus)
get_status
;;
getPpeEntries)
get_ppe_entries
;;
getTokenInfo)
get_token_info
;;
setGovernor)
read input
set_governor "$input"
;;
setMaxFreq)
read input
set_max_freq "$input"
;;
setOverclock)
read input
set_overclock "$input"
;;
getFrameEngine)
get_frame_engine
;;
*)
echo '{"error":"Invalid method"}'
;;
esac
;;
esac

View File

@ -0,0 +1,13 @@
{
"admin/status/npu": {
"title": "SoC Status",
"order": 15,
"action": {
"type": "view",
"path": "airoha_npu/status"
},
"depends": {
"acl": [ "luci-app-airoha-npu" ]
}
}
}

View File

@ -0,0 +1,26 @@
{
"luci-app-airoha-npu": {
"description": "Grant access to Airoha SoC status (NPU + CPU + Overclock)",
"read": {
"ubus": {
"luci.airoha_npu": [ "getStatus", "getPpeEntries", "getTokenInfo", "getFrameEngine" ]
},
"file": {
"/sys/kernel/debug/ppe/entries": [ "read" ],
"/sys/kernel/debug/ppe/bind": [ "read" ],
"/sys/devices/system/cpu/cpufreq/policy0/*": [ "read" ],
"/sys/kernel/debug/ieee80211/*/mt76/token_info": [ "read" ]
}
},
"write": {
"ubus": {
"luci.airoha_npu": [ "setGovernor", "setMaxFreq", "setOverclock" ]
},
"file": {
"/sys/devices/system/cpu/cpufreq/policy0/scaling_governor": [ "write" ],
"/sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq": [ "write" ],
"/dev/mem": [ "read", "write" ]
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.6.2
PKG_RELEASE:=163
PKG_RELEASE:=164
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@ -84,7 +84,7 @@ config PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy
config PACKAGE_$(PKG_NAME)_INCLUDE_Geoview
bool "Include Geoview"
select PACKAGE_geoview
default y if aarch64||arm||i386||x86_64
default y if aarch64||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy
bool "Include Haproxy"
@ -104,7 +104,7 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client
bool "Include Shadowsocks Rust Client"
depends on !i386
default y if aarch64||x86_64
default y if x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server
bool "Include Shadowsocks Rust Server"
@ -134,7 +134,7 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs
config PACKAGE_$(PKG_NAME)_INCLUDE_SingBox
bool "Include Sing-Box"
select PACKAGE_sing-box
default y if aarch64||i386||x86_64
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Geodata
bool "Include V2ray_Geodata"
@ -144,11 +144,11 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Geodata
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
bool "Include V2ray-Plugin (Shadowsocks Plugin)"
default y if aarch64||i386||x86_64
default y if i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
bool "Include Xray"
default y
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray_Plugin
bool "Include Xray-Plugin (Shadowsocks Plugin)"