Skip to main content
  1. Posts/

Orange Pi 5 Plus: Diagnosticando Hard Resets e Configurando Acesso Remoto

·10 minutos

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:

TesteResultado
CPU + VM 20% de carga, sem cap de frequênciaTravamento em ~30 segundos
Apenas CPU 20% de carga10 minutos estável
Apenas CPU 80% de carga, sem capTravamento
CPU + VM 100% de carga, cap 1,608 GHzEstável
CPU + VM 100% de carga, cap 1,8 GHzEstável
CPU + VM 100% de carga, cap 2,0 GHzTravamento ~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.service desabilitado.

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:

  • -4 forç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
  • -RemoteResize faz 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:

  1. pactl set-default-sink muda para onde novos streams vão.
  2. move_streams_to move 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:

  1. Túnel SSH abre (VNC na porta 5902).
  2. Pipe SSH de áudio inicia — parec no SBC transmite para pacat.exe no Windows.
  3. vnc-audio-switch.sh detecta a nova conexão e muda o sink padrão para auto_null, movendo todos os streams em reprodução.
  4. Sessão VNC abre com o áudio já saindo pelos alto-falantes do Windows.

Quando a sessão fecha:

  1. Sessão TCP do VNC cai; vnc-connect.ps1 encerra automaticamente.
  2. Pipe SSH de áudio é encerrado (taskkill /T no wrapper cmd.exe).
  3. vnc-audio-switch.sh detecta a porta 5902 inativa e volta para o sink HDMI físico.
  4. Áudio retoma na TV.

Resumo #

CamadaMudançaEfeito
Fonte de alimentaçãoSubstituída por unidade de 5 V / 4 APlaca estável; 3,7× de throughput vs. contorno com cap
Governor de CPUschedutilperformanceZero de latência de ramp-up
Governor de GPUsimple_ondemandperformanceClock consistente para workloads de GPU
VNCTigerVNC em :2, túnel SSH, localhost-only, auto-restartAcesso remoto headless estável
Clipboard VNCautocutsel bidirecionalCopiar/colar entre Windows e sessão VNC
Conexão VNCScript PowerShell com polling de porta + flag -4 no SSHUm clique, sem prompts, resolução correta
Saída automática VNCObservador de conexão TCP no PowerShellFecha o vncviewer corretamente quando a sessão cai
PolkitRegra silenciosa para NetworkManagerSem popup em todo login VNC
Roteamento de áudioSink null auto_null do PulseAudio + vnc-audio-switch.shÁudio segue a conexão/desconexão VNC automaticamente
Stream de áudioparec → pipe SSH → pacat.exe → PulseAudio WindowsÁudio de baixa latência sem abrir portas de rede extras
TCP do PipeWireDesabilitado em pipewire-pulse.confEvita conflito com o pipe de áudio SSH