Orange Pi 5 Plus: Diagnosticando Hard Resets e Configurando Acesso Remoto
A Placa #
Um Orange Pi 5 Plus rodando Ubuntu 26.04 LTS (aarch64). O SoC RK3588 tem 4× núcleos Cortex-A55 little e 4× Cortex-A76 big (até 2,4 GHz), 16 GB de RAM e uma GPU Mali-G610. Alimentação apenas por USB-C — sem conector barrel jack.
Esta placa é usada como um arcade/gabinete de retrogaming. A configuração de emulação é coberta em um post separado; este cobre a infraestrutura: estabilizar a placa e torná-la acessível.
Capítulo 1: Os Hard Resets Misteriosos #
Logo após a configuração, a placa começou a reiniciar abruptamente sob carga — sem kernel panic, sem log de OOM, sem desligamento gracioso. O PMIC simplesmente cortava a energia.
Reproduzindo o padrão #
Testes de stress com stress-ng rapidamente mapearam o limite:
| Teste | Resultado |
|---|---|
| CPU + VM 20% de carga, sem cap de frequência | Travamento em ~30 segundos |
| Apenas CPU 20% de carga | 10 minutos estável |
| Apenas CPU 80% de carga, sem cap | Travamento |
| CPU + VM 100% de carga, cap 1,608 GHz | Estável |
| CPU + VM 100% de carga, cap 1,8 GHz | Estável |
| CPU + VM 100% de carga, cap 2,0 GHz | Travamento ~2,5 min |
O worker de largura de banda de memória (--vm) era o gatilho. Adicionar pressão no
DRAM causava resets mesmo com 20% de utilização de CPU — carga exclusiva de CPU era
muito mais tolerante.
Causa raiz: fonte de alimentação insuficiente #
O Orange Pi 5 Plus exige 5 V / 4 A (20 W) sob carga. Importante: a placa não
implementa USB Power Delivery — nenhuma negociação PD acontece independentemente do
carregador conectado. O nó TCPM em
/sys/class/power_supply/tcpm-source-psy-6-0022/ sempre reporta online=0 e isso é
normal para esta placa, não uma falha. A capacidade PD do carregador é irrelevante.
A fonte original simplesmente não conseguia entregar 4 A de forma sustentada. Ela fornecia corrente suficiente em idle, mas no momento em que DRAM e CPU exigiam potência total simultaneamente, a tensão caía, o PMIC atingia a proteção de subtensão e cortava a energia instantaneamente.
O contorno (histórico) #
Enquanto aguardava a reposição, um serviço systemd (limit-cpufreq.service) limitou os
núcleos A76 big a 1,608 GHz e forçou schedutil. A placa ficou estável nesse teto
indefinidamente, mas perdeu cerca de 3× de throughput de emulação.
A solução #
Substituir a fonte por uma com capacidade mínima de 5 V / 4 A. Suporte a PD é irrelevante. Após a troca:
- Stress completo de CPU + VM a 2,4 GHz irrestrito passa 5 minutos sem falhas.
- Throughput de VM: ~19 000 → ~72 000 bogo ops/s (3,7×).
limit-cpufreq.servicedesabilitado.
Capítulo 2: Ajuste de Governor #
Com uma fonte saudável, não há motivo para throttling. CPU e GPU são travadas em
performance via um serviço systemd (gpu-governor.service).
- CPU: Todos os 8 núcleos na frequência máxima (A55 até 1,8 GHz, A76 até 2,4 GHz).
- GPU: Mali-G610 a 1000 MHz (topo da faixa 300–1000 MHz).
performance elimina completamente a latência de ramp-up. Para um gabinete que fica
ocioso no menu ou rodando um emulador a framerate fixo, a resposta transitória do
schedutil adiciona jitter sem nenhum benefício.
Para reverter aos padrões sensatos:
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo schedutil > "$gov"
done
echo simple_ondemand > /sys/class/devfreq/fb000000.gpu/governor
sudo systemctl disable --now gpu-governor.service
Capítulo 3: Acesso Remoto — VNC sobre SSH #
O gabinete roda headless na maior parte do tempo. O acesso remoto é via TigerVNC tunelado por SSH, de forma que nada fica exposto na rede.
Configuração do servidor #
O TigerVNC roda como o usuário local no display :2 (porta 5902), vinculado apenas
ao localhost. O serviço systemd (tigervncserver@:2.service) inicia no boot e reinicia
automaticamente em caso de crash:
[Service]
Restart=always
RestartSec=3
O xstartup do KDE desabilita o compositor (efeitos de composição são invisíveis sobre
VNC e apenas adicionam overhead) e executa autocutsel duas vezes para sincronização
bidirecional de clipboard:
#!/bin/bash
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
/usr/bin/dbus-launch --exit-with-session startplasma-x11 &
autocutsel -fork
autocutsel -selection PRIMARY -fork
Conectando a partir do Windows #
Um script PowerShell (vnc-connect.ps1) gerencia a conexão completa com um duplo clique:
túnel SSH, streaming de áudio, lançamento do VNC e saída automática quando a sessão
remota cai.
# Substitua estas três variáveis pela sua configuração
$SBC = "NNN.NNN.NNN.NNN" # endereço IP do SBC
$USER = "seu-usuario" # usuário SSH no SBC
$PORT = 5902
$PASSWD = "$env:USERPROFILE\.vnc-passwd"
$SCRIPTDIR = Split-Path -Parent $MyInvocation.MyCommand.Definition
$PADIR = "$SCRIPTDIR\pulseaudio\bin"
$PAEXE = "$PADIR\pulseaudio.exe"
$PACATEXE = "$PADIR\pacat.exe"
# Inicia o PulseAudio se não estiver rodando — mantido vivo entre sessões
if (Test-Path $PAEXE) {
if (-not (Get-Process pulseaudio -ErrorAction SilentlyContinue)) {
Start-Process $PAEXE -ArgumentList "--daemonize=no" `
-WorkingDirectory $PADIR -WindowStyle Hidden
Start-Sleep -Seconds 3
}
}
# Túnel SSH para VNC
$tunnel = Start-Process ssh `
-ArgumentList "-4 -L ${PORT}:localhost:${PORT} -N $USER@$SBC" `
-WindowStyle Hidden -PassThru
# Poll até a porta VNC estar acessível
$elapsed = 0
while ($elapsed -lt 15) {
$test = Test-NetConnection -ComputerName localhost -Port $PORT -WarningAction SilentlyContinue
if ($test.TcpTestSucceeded) { break }
Start-Sleep -Seconds 1; $elapsed++
}
# Stream de áudio PCM bruto do SBC via pipe SSH → pacat.exe → alto-falantes do Windows
$audioStream = $null
if (Test-Path $PACATEXE) {
$audioBat = "$env:TEMP\vnc-audio.bat"
"@echo off`ncd /d `"$PADIR`"`nssh -4 -o IPQoS=lowdelay -o Compression=no $USER@$SBC " +
"`"PULSE_RUNTIME_PATH=/run/user/1000/pulse parec --raw --format=s16le --rate=48000 " +
"--channels=2 --latency-msec=50 -d auto_null.monitor`" | " +
"pacat.exe --raw --format=s16le --rate=48000 --channels=2 --latency-msec=100 -p" `
| Set-Content $audioBat
$audioStream = Start-Process "cmd.exe" -ArgumentList "/c `"$audioBat`"" `
-WindowStyle Hidden -PassThru
}
$vnc = Start-Process vncviewer `
-ArgumentList "-FullScreen -RemoteResize -passwd `"$PASSWD`" localhost:$PORT" -PassThru
# Observa queda de sessão TCP — encerra vncviewer automaticamente para evitar o diálogo
# "connection closed" travado
$wasConnected = $false
while (-not $vnc.HasExited) {
Start-Sleep -Seconds 2
$conns = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().GetActiveTcpConnections()
$isConnected = [bool]($conns | Where-Object {
$_.RemoteEndPoint.Port -eq $PORT -and
$_.State -eq [System.Net.NetworkInformation.TcpState]::Established
})
if (-not $wasConnected -and $isConnected) { $wasConnected = $true }
if ($wasConnected -and -not $isConnected) {
Start-Sleep -Seconds 2
$vnc | Stop-Process -Force -ErrorAction SilentlyContinue
break
}
}
# Limpeza — /T mata cmd.exe E seus processos filhos SSH+pacat
Stop-Process -Id $tunnel.Id -Force -ErrorAction SilentlyContinue
if ($audioStream) { & taskkill /F /T /PID $audioStream.Id 2>&1 | Out-Null }
Remove-Item "$env:TEMP\vnc-audio.bat" -Force -ErrorAction SilentlyContinue
# pulseaudio.exe é intencionalmente mantido rodando para a próxima sessão
Detalhes importantes:
-4força IPv4 — o Windows às vezes resolve o hostname para IPv6, quebrando o túnel silenciosamente- O polling de porta substitui um sleep fixo; reconectar imediatamente após uma desconexão funciona de forma confiável
-RemoteResizefaz a sessão VNC se adaptar ao monitor em que for aberta- O áudio é transmitido como PCM bruto por uma segunda conexão SSH (veja o Capítulo 4)
- O
pulseaudio.exeé intencionalmente não encerrado ao sair — veja o Capítulo 4 - O observador TCP encerra o vncviewer automaticamente sem o diálogo de “connection closed”; fechar o vncviewer manualmente também encerra normalmente, pois
HasExitedé acionado
Execute sem janela de terminal via atalho:
powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File "vnc-connect.ps1"
Supressão do popup PolicyKit #
O KDE exibe um popup polkit em todo login VNC reclamando que conexões de rede estão
bloqueadas para sessões não locais. Silenciado com uma regra polkit em
/etc/polkit-1/rules.d/50-vnc-netmanager.rules concedendo ao usuário local
permissão silenciosa para ações do NetworkManager.
Capítulo 4: Streaming de Áudio pelo SSH #
O VNC não tem canal de áudio. A solução é uma segunda conexão SSH carregando PCM bruto da stack PulseAudio do SBC diretamente para o sistema de áudio do Windows — sem portas de rede extras, sem protocolo PulseAudio, sem regras de firewall além do túnel SSH existente.
O problema #
O gabinete alterna entre dois modos de uso:
- Local (HDMI + TV): o som deve sair pela saída HDMI do SBC.
- Remoto (VNC): o som deve sair pelos alto-falantes do Windows.
Um sink fixo não funciona. O sink deve mudar automaticamente quando uma sessão VNC conecta ou desconecta.
Sink null virtual #
O module-null-sink do PulseAudio cria um dispositivo de áudio virtual (auto_null) que
sempre existe independentemente de qual hardware físico está presente. Qualquer aplicação
que escreva nele pode ser monitorada e capturada. Este se torna o “output de áudio VNC” —
aplicações reproduzem para auto_null e o pipe SSH lê de auto_null.monitor.
O daemon de alternância #
~/vnc-audio-switch.sh roda como um daemon de autostart do KDE e consulta ss -tn a
cada 3 segundos por uma conexão TCP estabelecida na porta 5902:
#!/bin/bash
# Substitua /run/user/1000/pulse pelo runtime path do seu usuário (id -u mostra o UID)
export PULSE_RUNTIME_PATH=/run/user/1000/pulse
pgrep -f "vnc-audio-switch.sh" | grep -v "^$$" | xargs kill 2>/dev/null
sleep 1
CONNECTED=false
move_streams_to() {
local sink="$1"
pactl list sink-inputs short 2>/dev/null | awk '{print $1}' | while read -r id; do
pactl move-sink-input "$id" "$sink" 2>/dev/null
done
}
while true; do
if ss -tn | grep -q "ESTAB.*5902"; then
if [ "$CONNECTED" = false ]; then
CONNECTED=true
pactl set-default-sink auto_null 2>/dev/null
move_streams_to auto_null
fi
else
if [ "$CONNECTED" = true ]; then
CONNECTED=false
PHYSICAL=$(pactl list sinks short 2>/dev/null \
| grep -v auto_null | awk '{print $2}' | head -1)
if [ -n "$PHYSICAL" ]; then
pactl set-default-sink "$PHYSICAL" 2>/dev/null
move_streams_to "$PHYSICAL"
fi
fi
fi
sleep 3
done
Duas coisas acontecem em cada transição:
pactl set-default-sinkmuda para onde novos streams vão.move_streams_tomove explicitamente os streams já em reprodução — jogos e a UI do ES-DE que estavam rodando antes da mudança de estado da conexão.
O pipe SSH #
parec lê o monitor do sink null no SBC e escreve PCM estéreo de 16 bits bruto para
stdout. O pipe SSH o carrega para o Windows, onde pacat.exe o alimenta no PulseAudio:
parec --raw --format=s16le --rate=48000 --channels=2 --latency-msec=50 \
-d auto_null.monitor → SSH → pacat.exe --raw ... --latency-msec=100
Flags SSH para baixa latência:
-o IPQoS=lowdelay -o Compression=no
Compression=no importa: a compressão padrão do SSH adiciona latência variável que
aparece como falhas de áudio sob carga do emulador.
PulseAudio para Windows #
O build pgaskin do PulseAudio para Windows (v15) roda como um processo em background e
deve ser mantido vivo entre sessões — não encerrado ao sair do VNC. Reiniciá-lo
causa falhas silenciosas de reinicialização do waveout e deixa um arquivo PID obsoleto
que impede a próxima inicialização. O vnc-connect.ps1 só o inicia quando não está
rodando e nunca o encerra na limpeza.
Conflito TCP do PipeWire #
O Ubuntu 26.04 vem com PipeWire como servidor de áudio padrão. O PipeWire-Pulse pode
expor um socket TCP que conflita com a abordagem de áudio SSH bruto. Esse módulo TCP
deve ser desabilitado em ~/.config/pipewire/pipewire-pulse.conf para que os dois não
disputem a porta do protocolo PulseAudio.
Resultado final #
Quando vnc-connect.ps1 abre:
- Túnel SSH abre (VNC na porta 5902).
- Pipe SSH de áudio inicia —
parecno SBC transmite parapacat.exeno Windows. vnc-audio-switch.shdetecta a nova conexão e muda o sink padrão paraauto_null, movendo todos os streams em reprodução.- Sessão VNC abre com o áudio já saindo pelos alto-falantes do Windows.
Quando a sessão fecha:
- Sessão TCP do VNC cai;
vnc-connect.ps1encerra automaticamente. - Pipe SSH de áudio é encerrado (
taskkill /Tno wrapper cmd.exe). vnc-audio-switch.shdetecta a porta 5902 inativa e volta para o sink HDMI físico.- Áudio retoma na TV.
Resumo #
| Camada | Mudança | Efeito |
|---|---|---|
| Fonte de alimentação | Substituída por unidade de 5 V / 4 A | Placa estável; 3,7× de throughput vs. contorno com cap |
| Governor de CPU | schedutil → performance | Zero de latência de ramp-up |
| Governor de GPU | simple_ondemand → performance | Clock consistente para workloads de GPU |
| VNC | TigerVNC em :2, túnel SSH, localhost-only, auto-restart | Acesso remoto headless estável |
| Clipboard VNC | autocutsel bidirecional | Copiar/colar entre Windows e sessão VNC |
| Conexão VNC | Script PowerShell com polling de porta + flag -4 no SSH | Um clique, sem prompts, resolução correta |
| Saída automática VNC | Observador de conexão TCP no PowerShell | Fecha o vncviewer corretamente quando a sessão cai |
| Polkit | Regra silenciosa para NetworkManager | Sem popup em todo login VNC |
| Roteamento de áudio | Sink null auto_null do PulseAudio + vnc-audio-switch.sh | Áudio segue a conexão/desconexão VNC automaticamente |
| Stream de áudio | parec → pipe SSH → pacat.exe → PulseAudio Windows | Áudio de baixa latência sem abrir portas de rede extras |
| TCP do PipeWire | Desabilitado em pipewire-pulse.conf | Evita conflito com o pipe de áudio SSH |