diff --git a/kvmapp/system/init.d/S95nanokvm b/kvmapp/system/init.d/S95nanokvm index 342643a9..e62f328e 100755 --- a/kvmapp/system/init.d/S95nanokvm +++ b/kvmapp/system/init.d/S95nanokvm @@ -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" diff --git a/server/dl_lib/libkvm_mmf.so b/server/dl_lib/libkvm_mmf.so index f09abd4c..687ce6bd 100644 Binary files a/server/dl_lib/libkvm_mmf.so and b/server/dl_lib/libkvm_mmf.so differ diff --git a/support/sg2002/additional/kvm_mmf/src/kvm_mmf.cpp b/support/sg2002/additional/kvm_mmf/src/kvm_mmf.cpp index 6eaaf970..dc9f7c13 100644 --- a/support/sg2002/additional/kvm_mmf/src/kvm_mmf.cpp +++ b/support/sg2002/additional/kvm_mmf/src/kvm_mmf.cpp @@ -5,12 +5,19 @@ #include "sample_comm.h" #include +#include #include #include #include +#include #include #include +#include +#include #include +#include + +#include 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(); } diff --git a/support/sg2002/kvm_system/main/lib/system_init/system_init.cpp b/support/sg2002/kvm_system/main/lib/system_init/system_init.cpp index bcb28e68..717ceb30 100644 --- a/support/sg2002/kvm_system/main/lib/system_init/system_init.cpp +++ b/support/sg2002/kvm_system/main/lib/system_init/system_init.cpp @@ -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) diff --git a/support/sg2002/kvm_system/main/lib/system_init/system_init.h b/support/sg2002/kvm_system/main/lib/system_init/system_init.h index 47959bf3..414be36a 100644 --- a/support/sg2002/kvm_system/main/lib/system_init/system_init.h +++ b/support/sg2002/kvm_system/main/lib/system_init/system_init.h @@ -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_ diff --git a/support/sg2002/kvm_system/main/src/main.cpp b/support/sg2002/kvm_system/main/src/main.cpp index 0e4f69f0..88abf3a7 100644 --- a/support/sg2002/kvm_system/main/src/main.cpp +++ b/support/sg2002/kvm_system/main/src/main.cpp @@ -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){