fix: prevent black screen after application upgrades

Serialize service operations and finish migration before starting services.
Install the startup script atomically, remove recursive restarts, and keep
the migration marker until required changes succeed.

Claim video ownership before clearing orphan VENC/VPSS resources, always
finish recovery SYS cleanup, and retain resource state on teardown failures.
Rebuild the bundled libkvm_mmf.so with these fixes.

Validated cross-builds, ABI and recovery failure paths, plus a 2.4.3 to 2.5.3
upgrade on hardware with 1080p browser WebRTC and MJPEG playback.
This commit is contained in:
916BGAI
2026-11-04 19:45:09 +08:00
parent 4b54fc4768
commit 44c420e28b
6 changed files with 199 additions and 135 deletions

View File

@@ -1,6 +1,9 @@
#!/bin/sh
# nanokvm Rev3.1
exec 9>/run/nanokvm-service.lock || exit 1
flock -n 9 || { echo "NanoKVM service operation already in progress" >&2; exit 1; }
wait_for_exit() {
process="$1"
timeout="$2"
@@ -34,43 +37,42 @@ stop_process() {
fi
killall -KILL "$process" 2>/dev/null || true
wait_for_exit "$process" 2 || true
wait_for_exit "$process" 2 || {
echo "Failed to stop $process" >&2
return 1
}
}
stop_services() {
# The server owns VI/VPSS. Let it release MMF before stopping kvm_system.
stop_process NanoKVM-Server
stop_process kvm_system
rm -rf /tmp/kvm_system /tmp/server
stop_process NanoKVM-Server || return 1
stop_process kvm_system || return 1
}
start_services() {
cp -r /kvmapp/kvm_system /tmp/
if pidof NanoKVM-Server >/dev/null 2>&1 && pidof kvm_system >/dev/null 2>&1; then
return 0
fi
if pidof NanoKVM-Server >/dev/null 2>&1 || pidof kvm_system >/dev/null 2>&1; then
echo "NanoKVM is partially running; use restart" >&2
return 1
fi
rm -rf /tmp/kvm_system /tmp/server || return 1
cp -r /kvmapp/kvm_system /tmp/ || return 1
if [ -f /kvmapp/kvm_new_app ]; then
touch /tmp/.nanokvm_migrating
/tmp/kvm_system/kvm_system &
# The legacy migration invokes this init script recursively and starts
# the server itself. Wait for that single migration path to finish.
migration_wait=0
while [ "$migration_wait" -lt 30 ]; do
if [ ! -f /kvmapp/kvm_new_app ] && pidof NanoKVM-Server >/dev/null 2>&1; then
rm -f /tmp/.nanokvm_migrating
return
fi
sleep 1
migration_wait=$((migration_wait + 1))
done
rm -f /tmp/.nanokvm_migrating
else
/tmp/kvm_system/kvm_system &
/tmp/kvm_system/kvm_system --migrate 9>&-
case "$?" in
0) ;;
2) sync; reboot; return "$?" ;;
*) echo "NanoKVM migration failed" >&2; return 1 ;;
esac
fi
if ! pidof NanoKVM-Server >/dev/null 2>&1; then
rm -rf /tmp/server
cp -r /kvmapp/server /tmp/
/tmp/server/NanoKVM-Server &
fi
cp -r /kvmapp/server /tmp/ || return 1
# Daemons must not inherit the service-operation lock.
/tmp/kvm_system/kvm_system 9>&- &
/tmp/server/NanoKVM-Server 9>&- &
}
case "$1" in
@@ -118,20 +120,17 @@ case "$1" in
fi
# Start services
start_services
start_services || exit 1
;;
stop)
stop_services
stop_services || exit 1
echo "OK"
;;
restart)
if [ -f /tmp/.nanokvm_migrating ]; then
exit 0
fi
stop_services
start_services
stop_services || exit 1
start_services || exit 1
sync
echo "OK"

Binary file not shown.

View File

@@ -5,12 +5,19 @@
#include "sample_comm.h"
#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <limits>
#include <mutex>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
#include <linux/cvi_vc_drv_ioctl.h>
namespace kvm_mmf {
namespace {
@@ -37,6 +44,8 @@ SAMPLE_VI_CONFIG_S vi_config = {};
bool system_ready = false, group_created = false, group_started = false;
bool vi_bound = false, vi_started = false;
uint64_t next_token[2] = {}, last_pts[2] = {};
int owner_fd = -1; // Held until process exit, including across HDMI restarts.
bool stale_resources_cleared = false;
int channel_id(Codec codec) { return codec == H264 ? 0 : 1; }
bool valid_codec(Codec codec) { return codec == H264 || codec == MJPEG; }
@@ -48,6 +57,64 @@ bool check(int result, const char *operation)
return false;
}
bool unbind_source(MOD_ID_E module, int device, int channel)
{
MMF_CHN_S source = {module, device, channel};
MMF_BIND_DEST_S destinations = {};
if (!check(CVI_SYS_GetBindbySrc(&source, &destinations), "GetBindbySrc")) return false;
for (unsigned i = 0; i < destinations.u32Num; ++i) {
if (!check(CVI_SYS_UnBind(&source, &destinations.astMmfChn[i]), "UnBind stale stream")) return false;
}
return true;
}
bool clear_stale_encoder(int channel)
{
char device[32];
snprintf(device, sizeof(device), "/dev/%s%d", CVI_VC_DRV_ENCODER_DEV_NAME, channel);
int fd = open(device, O_RDWR | O_CLOEXEC);
if (fd < 0) return check(CVI_FAILURE, "open stale encoder");
VENC_CHN_ATTR_S attr = {};
int result = ioctl(fd, CVI_VC_VENC_GET_CHN_ATTR, &attr);
if (result == CVI_SUCCESS) {
fprintf(stderr, "[kvm_mmf] clearing stale VENC channel %d\n", channel);
// The SDK wrapper counts channels created in this process. Its
// DestroyChn cannot adopt an orphan without corrupting that count.
// Use the SDK's kernel ABI only for recovery of the previous owner.
result = ioctl(fd, CVI_VC_VENC_STOP_RECV_FRAME);
if (result == CVI_SUCCESS) result = ioctl(fd, CVI_VC_VENC_RESET_CHN);
if (result == CVI_SUCCESS) result = ioctl(fd, CVI_VC_VENC_DESTROY_CHN);
} else if (result == CVI_ERR_VENC_UNEXIST) {
result = CVI_SUCCESS;
}
close(fd);
return check(result, "clear stale VENC channel");
}
bool clear_stale_resources()
{
if (!unbind_source(CVI_ID_VI, 0, 0)) return false;
for (int channel = 0; channel < VPSS_MAX_CHN_NUM; ++channel) {
if (!unbind_source(CVI_ID_VPSS, group, channel)) return false;
}
for (int channel = 0; channel < 2; ++channel) {
if (!clear_stale_encoder(channel)) return false;
}
VPSS_GRP_ATTR_S attr = {};
int result = CVI_VPSS_GetGrpAttr(group, &attr);
if (result == CVI_SUCCESS) {
fprintf(stderr, "[kvm_mmf] clearing stale VPSS group %d\n", group);
for (int channel = 0; channel < VPSS_MAX_CHN_NUM; ++channel) {
if (!check(CVI_VPSS_DisableChn(group, channel), "Disable stale VPSS channel")) return false;
}
if (!check(CVI_VPSS_StopGrp(group), "Stop stale VPSS group") ||
!check(CVI_VPSS_DestroyGrp(group), "Destroy stale VPSS group")) return false;
} else if (result != CVI_ERR_VPSS_UNEXIST) {
return check(result, "Get stale VPSS group");
}
return true;
}
bool release_locked(Codec codec)
{
Channel &ch = channels[codec];
@@ -121,26 +188,55 @@ bool configure_output(Codec codec, uint16_t width, uint16_t height, uint16_t fps
return true;
}
void deinitialize_locked()
bool deinitialize_locked()
{
stop_locked(H264);
stop_locked(MJPEG);
if (vi_bound) check(SAMPLE_COMM_VI_UnBind_VPSS(0, 0, group), "VI_UnBind_VPSS");
if (vi_started) {
check(SAMPLE_COMM_VI_DestroyIsp(&vi_config), "DestroyIsp");
check(SAMPLE_COMM_VI_DestroyVi(&vi_config), "DestroyVi");
bool h264_stopped = stop_locked(H264);
bool mjpeg_stopped = stop_locked(MJPEG);
if (!h264_stopped || !mjpeg_stopped) return false;
if (vi_bound) {
if (!check(SAMPLE_COMM_VI_UnBind_VPSS(0, 0, group), "VI_UnBind_VPSS")) return false;
vi_bound = false;
}
if (group_started) check(CVI_VPSS_StopGrp(group), "StopGrp");
if (group_created) check(CVI_VPSS_DestroyGrp(group), "DestroyGrp");
if (system_ready) SAMPLE_COMM_SYS_Exit();
system_ready = group_created = group_started = vi_bound = vi_started = false;
channels[H264] = Channel{};
channels[MJPEG] = Channel{};
if (vi_started) {
SAMPLE_COMM_VI_DestroyIsp(&vi_config);
SAMPLE_COMM_VI_DestroyVi(&vi_config);
vi_started = false;
}
if (group_started) {
if (!check(CVI_VPSS_StopGrp(group), "StopGrp")) return false;
group_started = false;
}
if (group_created) {
if (!check(CVI_VPSS_DestroyGrp(group), "DestroyGrp")) return false;
group_created = false;
}
if (system_ready) {
if (!check(CVI_SYS_Exit(), "SYS_Exit") || !check(CVI_VB_Exit(), "VB_Exit")) return false;
system_ready = false;
}
return true;
}
bool initialize_locked()
{
if (vi_started) return true;
if (owner_fd < 0) {
int fd = open("/run/nanokvm-video.lock", O_CREAT | O_RDWR | O_CLOEXEC, 0600);
if (fd < 0 || flock(fd, LOCK_EX | LOCK_NB) != 0) {
fprintf(stderr, "[kvm_mmf] cannot acquire video ownership: %s\n", strerror(errno));
if (fd >= 0) close(fd);
return false;
}
owner_fd = fd;
}
if (!stale_resources_cleared) {
if (!check(CVI_SYS_Init(), "SYS_Init for recovery")) return false;
const bool cleared = clear_stale_resources();
const bool exited = check(CVI_SYS_Exit(), "SYS_Exit after recovery");
if (!cleared || !exited) return false;
stale_resources_cleared = true;
}
if (system_ready && !deinitialize_locked()) return false;
SAMPLE_INI_CFG_S ini = {};
PIC_SIZE_E picture;
SIZE_S size = {};
@@ -278,7 +374,7 @@ bool initialize()
bool restart()
{
std::scoped_lock lock(channel_mutex[H264], channel_mutex[MJPEG]);
deinitialize_locked();
if (!deinitialize_locked()) return false;
return initialize_locked();
}

View File

@@ -10,14 +10,14 @@ using namespace maix::sys;
extern kvm_sys_state_t kvm_sys_state;
extern kvm_oled_state_t kvm_oled_state;
static void write_hdmi_state(uint8_t active)
static bool write_hdmi_state(uint8_t active)
{
char temp_path[] = "/kvmapp/kvm/.state.init.XXXXXX";
const char *state = active != 0 ? "1\n" : "0\n";
int fd = mkstemp(temp_path);
if(fd < 0){
printf("failed to create HDMI state file: %d\n", errno);
return;
return false;
}
int failure = 0;
@@ -40,6 +40,7 @@ static void write_hdmi_state(uint8_t active)
unlink(temp_path);
printf("failed to publish HDMI state file: %d\n", failure);
}
return failure == 0;
}
uint8_t get_hdmi_version()
@@ -88,38 +89,45 @@ void Production_testing_patch(void)
system("sync");
}
void new_app_init(void)
static bool init_command(const char *command)
{
if(system(command) == 0) return true;
fprintf(stderr, "[kvms] migration failed: %s\n", command);
return false;
}
AppInitResult new_app_init(void)
{
// Update the necessary scripts
system("rm -f /boot/logo.jpeg");
system("cp -f /kvmapp/system/update-nanokvm.py /etc/kvm/");
if(!init_command("mkdir -p /etc/kvm /kvmapp/kvm")) return APP_INIT_FAILED;
if(!init_command("cp -f /kvmapp/system/update-nanokvm.py /etc/kvm/")) return APP_INIT_FAILED;
system("rm -f /etc/init.d/S02udisk");
system("cp -f /kvmapp/system/init.d/S00kmod /etc/init.d/");
system("cp -f /kvmapp/system/init.d/S01fs /etc/init.d/");
system("cp -f /kvmapp/system/init.d/S03usbdev /etc/init.d/");
system("cp -f /kvmapp/system/init.d/S15kvmhwd /etc/init.d/");
system("cp -f /kvmapp/system/init.d/S30eth /etc/init.d/");
system("cp -f /kvmapp/system/init.d/S50sshd /etc/init.d/");
if(!init_command("cp -f /kvmapp/system/init.d/S00kmod /etc/init.d/")) return APP_INIT_FAILED;
if(!init_command("cp -f /kvmapp/system/init.d/S01fs /etc/init.d/")) return APP_INIT_FAILED;
if(!init_command("cp -f /kvmapp/system/init.d/S03usbdev /etc/init.d/")) return APP_INIT_FAILED;
if(!init_command("cp -f /kvmapp/system/init.d/S15kvmhwd /etc/init.d/")) return APP_INIT_FAILED;
if(!init_command("cp -f /kvmapp/system/init.d/S30eth /etc/init.d/")) return APP_INIT_FAILED;
if(!init_command("cp -f /kvmapp/system/init.d/S50sshd /etc/init.d/")) return APP_INIT_FAILED;
if(kvm_wifi_exist()) {
system("cp -f /kvmapp/system/init.d/S30wifi /etc/init.d/");
if(!init_command("cp -f /kvmapp/system/init.d/S30wifi /etc/init.d/")) return APP_INIT_FAILED;
} else {
system("rm -f /etc/init.d/S30wifi");
}
// if exit /etc/init.d/S98tailscaled then cp -f /kvmapp/system/init.d/S98tailscaled /etc/init.d/
if(access("/etc/init.d/S98tailscaled", F_OK) == 0){
system("cp -f /kvmapp/system/init.d/S98tailscaled /etc/init.d/");
if(!init_command("cp -f /kvmapp/system/init.d/S98tailscaled /etc/init.d/")) return APP_INIT_FAILED;
}
// rmmod soph_saradc
system("rmmod soph_saradc");
system("rm -f /mnt/system/ko/soph_saradc.ko");
// PCIe Patch
// system("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/");
if(access("/kvmapp/jpg_stream/dl_lib/libmaixcam_lib.so", F_OK) != 0){
system("cp -f /kvmapp/system/init.d/S95nanokvm /etc/init.d/");
}
// Keep an already running legacy shell on its original script inode.
if(!init_command("cp /kvmapp/system/init.d/S95nanokvm /etc/init.d/.S95nanokvm.new && "
"chmod 755 /etc/init.d/.S95nanokvm.new && "
"mv -f /etc/init.d/.S95nanokvm.new /etc/init.d/S95nanokvm")) return APP_INIT_FAILED;
// Remove unnecessary components to speed up boot time
system("rm -f /etc/init.d/S04backlight");
@@ -129,90 +137,45 @@ void new_app_init(void)
system("rm -f /etc/init.d/S99*");
// Add necessary configuration files for program execution
system("mkdir /kvmapp/kvm");
system("mkdir /etc/kvm");
system("echo 0 > /kvmapp/kvm/now_fps");
system("echo 30 > /kvmapp/kvm/fps");
system("echo 2000 > /kvmapp/kvm/qlty");
system("echo h264 > /kvmapp/kvm/type");
write_hdmi_state(0);
system("touch /etc/kvm/frame_detact");
if(!init_command("echo 0 > /kvmapp/kvm/now_fps && echo 30 > /kvmapp/kvm/fps && "
"echo 2000 > /kvmapp/kvm/qlty && echo h264 > /kvmapp/kvm/type && "
"touch /etc/kvm/frame_detact")) return APP_INIT_FAILED;
if(!write_hdmi_state(0)) return APP_INIT_FAILED;
// rm jpg_stream & kvm_stream
system("rm -rf /kvmapp/jpg_stream");
system("rm -f /kvmapp/kvm_system/kvm_stream"); // Cannot delete temporarily; the old production test script uses this file to determine if the download is complete
system("rm -f /kvmapp/kvm_new_app");
system("sync");
// system("/etc/init.d/S95nanokvm restart");
// update ko
FILE *fp;
uint8_t RW_Data_0[30];
uint8_t RW_Data_1[30];
fp = popen("md5sum /mnt/system/ko/soph_mipi_rx.ko | grep 086ed01749188975afaa40fb569374f8 | awk '{print $2}'", "r");
if ( NULL == fp )
{
pclose(fp);
// return;
}
fgets((char*)RW_Data_0, 10, fp);
pclose(fp);
fp = popen("md5sum /mnt/system/ko/soph_mipi_rx.ko | grep 69be7eeded3777f750480a5dd5a1aa26 | awk '{print $2}'", "r");
if ( NULL == fp )
{
pclose(fp);
// return;
}
fgets((char*)RW_Data_1, 10, fp);
pclose(fp);
FILE *fp = popen("md5sum /mnt/system/ko/soph_mipi_rx.ko", "r");
if(!fp) return APP_INIT_FAILED;
char digest[33] = {};
bool digest_read = fscanf(fp, "%32s", digest) == 1;
int digest_status = pclose(fp);
if(!digest_read || digest_status != 0) return APP_INIT_FAILED;
int8_t hdmi_ver = -1;
if(access("/etc/kvm/hdmi_version", F_OK) == 0){
uint8_t RW_Data[2];
FILE *fp = fopen("/etc/kvm/hdmi_version", "r");
fread(RW_Data, sizeof(char), 2, fp);
if(!fp) return APP_INIT_FAILED;
int version = fgetc(fp);
fclose(fp);
if(RW_Data[0] == 'c') hdmi_ver = 1;
else if(RW_Data[0] == 'u') hdmi_ver = 2;
else if(RW_Data[0] == 'd') hdmi_ver = 2;
if(version == EOF) return APP_INIT_FAILED;
if(version == 'c') hdmi_ver = 1;
else if(version == 'u' || version == 'd') hdmi_ver = 2;
}
// system("/etc/init.d/S03usbdev stop_start");
create_temp_watchdog();
if(hdmi_ver == 2){
if(RW_Data_1[0] != '/'){
system("cp /kvmapp/system/ko/soph_mipi_rx.ko /mnt/system/ko/soph_mipi_rx.ko");
system("sync");
system("reboot");
} else {
system("sync");
system("/etc/init.d/S15kvmhwd start");
system("/etc/init.d/S95nanokvm restart");
}
} else {
if((RW_Data_0[0] != '/') && (RW_Data_1[0] != '/')){
system("cp /kvmapp/system/ko/soph_mipi_rx.ko /mnt/system/ko/soph_mipi_rx.ko");
system("sync");
system("reboot");
} else {
system("sync");
system("/etc/init.d/S15kvmhwd start");
system("/etc/init.d/S95nanokvm restart");
}
bool current_driver = strcmp(digest, "69be7eeded3777f750480a5dd5a1aa26") == 0 ||
(hdmi_ver != 2 && strcmp(digest, "086ed01749188975afaa40fb569374f8") == 0);
if(!current_driver){
if(!init_command("cp /kvmapp/system/ko/soph_mipi_rx.ko /mnt/system/ko/soph_mipi_rx.ko")) return APP_INIT_FAILED;
}
if(access("/root/old/kvm_new_img", F_OK) == 0){
system("rm -r /root/old");
}
system("sync");
system("killall NanoKVM-Server");
system("rm -r /tmp/server");
system("cp -r /kvmapp/server /tmp/");
system("/tmp/server/NanoKVM-Server &");
if(unlink("/kvmapp/kvm_new_app") != 0) return APP_INIT_FAILED;
sync();
return current_driver ? APP_INIT_OK : APP_INIT_REBOOT;
}
void build_complete_resolv(void)

View File

@@ -3,7 +3,8 @@
#include "config.h"
void new_img_init(void);
void new_app_init(void);
enum AppInitResult { APP_INIT_OK = 0, APP_INIT_FAILED = 1, APP_INIT_REBOOT = 2 };
AppInitResult new_app_init(void);
void Production_testing_patch(void);
#endif // SYSTEM_INIT_H_

View File

@@ -192,10 +192,15 @@ int main(int argc, char* argv[])
new_img_init();
}
// Execute only when it's a new app
const bool migrate_only = argc == 2 && strcmp(argv[1], "--migrate") == 0;
if(access("/kvmapp/kvm_new_app", F_OK) == 0){
new_app_init();
AppInitResult result = new_app_init();
if(migrate_only || result == APP_INIT_FAILED) return result;
// A legacy init script starts us without --migrate and can still be
// copying the server. Boot once into the newly installed init script.
return system("reboot") == 0 ? 0 : 1;
}
if(migrate_only) return APP_INIT_OK;
// only for production testing
if (access("/tmp/S49kvmtest", F_OK) == 0){