diff --git a/luci-app-airoha-npu/LICENSE b/luci-app-airoha-npu/LICENSE new file mode 100644 index 00000000..ce0c9b71 --- /dev/null +++ b/luci-app-airoha-npu/LICENSE @@ -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 diff --git a/luci-app-airoha-npu/Makefile b/luci-app-airoha-npu/Makefile new file mode 100644 index 00000000..84e87760 --- /dev/null +++ b/luci-app-airoha-npu/Makefile @@ -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 diff --git a/luci-app-airoha-npu/README.md b/luci-app-airoha-npu/README.md new file mode 100644 index 00000000..291a8360 --- /dev/null +++ b/luci-app-airoha-npu/README.md @@ -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 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) diff --git a/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js b/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js new file mode 100644 index 00000000..3944f7cd --- /dev/null +++ b/luci-app-airoha-npu/htdocs/luci-static/resources/view/airoha_npu/status.js @@ -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;i0.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 +}); diff --git a/luci-app-airoha-npu/po/es/luci-app-airoha-npu.po b/luci-app-airoha-npu/po/es/luci-app-airoha-npu.po new file mode 100644 index 00000000..f7cc0b5c --- /dev/null +++ b/luci-app-airoha-npu/po/es/luci-app-airoha-npu.po @@ -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:" diff --git a/luci-app-airoha-npu/po/templates/luci-app-airoha-npu.pot b/luci-app-airoha-npu/po/templates/luci-app-airoha-npu.pot new file mode 100644 index 00000000..6ac192e9 --- /dev/null +++ b/luci-app-airoha-npu/po/templates/luci-app-airoha-npu.pot @@ -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 "" diff --git a/luci-app-airoha-npu/root/usr/libexec/rpcd/luci.airoha_npu b/luci-app-airoha-npu/root/usr/libexec/rpcd/luci.airoha_npu new file mode 100755 index 00000000..329bdad6 --- /dev/null +++ b/luci-app-airoha-npu/root/usr/libexec/rpcd/luci.airoha_npu @@ -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;i0) 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 diff --git a/luci-app-airoha-npu/root/usr/share/luci/menu.d/luci-app-airoha-npu.json b/luci-app-airoha-npu/root/usr/share/luci/menu.d/luci-app-airoha-npu.json new file mode 100644 index 00000000..33806518 --- /dev/null +++ b/luci-app-airoha-npu/root/usr/share/luci/menu.d/luci-app-airoha-npu.json @@ -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" ] + } + } +} diff --git a/luci-app-airoha-npu/root/usr/share/rpcd/acl.d/luci-app-airoha-npu.json b/luci-app-airoha-npu/root/usr/share/rpcd/acl.d/luci-app-airoha-npu.json new file mode 100644 index 00000000..fdd5b915 --- /dev/null +++ b/luci-app-airoha-npu/root/usr/share/rpcd/acl.d/luci-app-airoha-npu.json @@ -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" ] + } + } + } +} diff --git a/luci-app-airoha-npu/screenshots/cpu-frequency.png b/luci-app-airoha-npu/screenshots/cpu-frequency.png new file mode 100644 index 00000000..ac48598d Binary files /dev/null and b/luci-app-airoha-npu/screenshots/cpu-frequency.png differ diff --git a/luci-app-airoha-npu/screenshots/npu-offload-engine.png b/luci-app-airoha-npu/screenshots/npu-offload-engine.png new file mode 100644 index 00000000..4d398d36 Binary files /dev/null and b/luci-app-airoha-npu/screenshots/npu-offload-engine.png differ diff --git a/luci-app-airoha-npu/screenshots/ppe-flow-table.png b/luci-app-airoha-npu/screenshots/ppe-flow-table.png new file mode 100644 index 00000000..2562b0d7 Binary files /dev/null and b/luci-app-airoha-npu/screenshots/ppe-flow-table.png differ diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index 7086e4c7..f3acbec4 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -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)"