diff --git a/server/dl_lib/libkvm.so b/server/dl_lib/libkvm.so index 79e2587..8a5a412 100644 Binary files a/server/dl_lib/libkvm.so and b/server/dl_lib/libkvm.so differ diff --git a/support/sg2002/additional/kvm/include/internal/vi_state_shared.hpp b/support/sg2002/additional/kvm/include/internal/vi_state_shared.hpp deleted file mode 100644 index 8e1c98c..0000000 --- a/support/sg2002/additional/kvm/include/internal/vi_state_shared.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef VI_STATE_SHARED_HPP_ -#define VI_STATE_SHARED_HPP_ - -#include -#include -#include -#include -#include -#include - -namespace vi_state_shared { - -struct State { - uint32_t dev_fps; - uint32_t fps; - uint32_t width_gt; - uint32_t width_ls; - uint32_t height_gt; - uint32_t height_ls; -}; - -uint8_t refresh(); - -namespace detail { - -static const char path[] = "/dev/shm/nanokvm_vi_state"; -static const uint32_t magic = 0x4e4b5649U; -static const uint32_t version = 1U; - -struct SharedState { - uint32_t sequence; - uint32_t magic; - uint32_t version; - uint32_t updated_ms; - State state; -}; - -inline uint32_t monotonic_ms() -{ - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - return (uint32_t)((uint64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000); -} - -inline const SharedState *map_reader() -{ - static const SharedState *shared = NULL; - if (shared != NULL) { - return shared; - } - - int fd = open(path, O_RDONLY | O_CLOEXEC); - if (fd < 0) { - return NULL; - } - struct stat stat_buf; - if (fstat(fd, &stat_buf) != 0 || stat_buf.st_size < (off_t)sizeof(*shared)) { - close(fd); - return NULL; - } - void *addr = mmap(NULL, sizeof(*shared), PROT_READ, MAP_SHARED, fd, 0); - close(fd); - if (addr == MAP_FAILED) { - return NULL; - } - shared = (const SharedState *)addr; - return shared; -} - -} // namespace detail - -inline bool read(State *state, uint32_t max_age_ms) -{ - const detail::SharedState *shared = detail::map_reader(); - if (shared == NULL) { - return false; - } - - for (int attempt = 0; attempt < 3; attempt++) { - uint32_t sequence = __atomic_load_n(&shared->sequence, __ATOMIC_ACQUIRE); - if (sequence & 1U) { - continue; - } - uint32_t magic = __atomic_load_n(&shared->magic, __ATOMIC_RELAXED); - uint32_t version = __atomic_load_n(&shared->version, __ATOMIC_RELAXED); - uint32_t updated_ms = __atomic_load_n(&shared->updated_ms, __ATOMIC_RELAXED); - State value; - value.dev_fps = __atomic_load_n(&shared->state.dev_fps, __ATOMIC_RELAXED); - value.fps = __atomic_load_n(&shared->state.fps, __ATOMIC_RELAXED); - value.width_gt = __atomic_load_n(&shared->state.width_gt, __ATOMIC_RELAXED); - value.width_ls = __atomic_load_n(&shared->state.width_ls, __ATOMIC_RELAXED); - value.height_gt = __atomic_load_n(&shared->state.height_gt, __ATOMIC_RELAXED); - value.height_ls = __atomic_load_n(&shared->state.height_ls, __ATOMIC_RELAXED); - uint32_t final_sequence = __atomic_load_n(&shared->sequence, __ATOMIC_ACQUIRE); - if (sequence == final_sequence && !(final_sequence & 1U) && - magic == detail::magic && version == detail::version && - detail::monotonic_ms() - updated_ms <= max_age_ms) { - *state = value; - return true; - } - } - return false; -} - -} // namespace vi_state_shared - -#endif // VI_STATE_SHARED_HPP_ diff --git a/support/sg2002/additional/kvm/include/internal/vi_state_writer.hpp b/support/sg2002/additional/kvm/include/internal/vi_state_writer.hpp new file mode 100644 index 0000000..a4684c6 --- /dev/null +++ b/support/sg2002/additional/kvm/include/internal/vi_state_writer.hpp @@ -0,0 +1,12 @@ +#ifndef VI_STATE_WRITER_HPP_ +#define VI_STATE_WRITER_HPP_ + +#include + +namespace vi_state_shared { + +uint8_t refresh(); + +} // namespace vi_state_shared + +#endif // VI_STATE_WRITER_HPP_ diff --git a/support/sg2002/additional/kvm/include/vi_state_shared.hpp b/support/sg2002/additional/kvm/include/vi_state_shared.hpp new file mode 100644 index 0000000..129b26e --- /dev/null +++ b/support/sg2002/additional/kvm/include/vi_state_shared.hpp @@ -0,0 +1,228 @@ +#ifndef VI_STATE_SHARED_HPP_ +#define VI_STATE_SHARED_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace vi_state_shared { + +struct State { + uint32_t dev_fps; + uint32_t fps; + uint32_t width_gt; + uint32_t width_ls; + uint32_t height_gt; + uint32_t height_ls; +}; + +enum Field : uint32_t { + FIELD_NONE = 0U, + FIELD_DEV_FPS = 1U << 0, + FIELD_FPS = 1U << 1, + FIELD_ERROR_COUNTER = 1U << 2, +}; + +enum ProcReadStatus { + PROC_READ_OK, + PROC_READ_OPEN_FAILED, + PROC_READ_NO_KNOWN_FIELDS, +}; + +enum ReadStatus { + READ_OK, + READ_UNAVAILABLE, + READ_INVALID, + READ_BUSY, + READ_STALE, +}; + +inline uint32_t monotonic_ms() +{ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (uint32_t)((uint64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000); +} + +inline const char *shared_path() +{ + return "/dev/shm/nanokvm_vi_state"; +} + +inline uint32_t parse(FILE *fp, State *state) +{ + if (fp == NULL || state == NULL) { + return FIELD_NONE; + } + + *state = State(); + uint32_t fields = FIELD_NONE; + char line[256]; + char field[32]; + unsigned int value; + while (fgets(line, sizeof(line), fp) != NULL) { + if (sscanf(line, " %31[^ :\t] %*[: \t] %u", field, &value) != 2) { + continue; + } + if (strcmp(field, "VIDevFPS") == 0) { + state->dev_fps = value; + fields |= FIELD_DEV_FPS; + } else if (strcmp(field, "VIFPS") == 0) { + state->fps = value; + fields |= FIELD_FPS; + } else if (strcmp(field, "VICsiCh0WidthGTCnt") == 0) { + state->width_gt = value; + fields |= FIELD_ERROR_COUNTER; + } else if (strcmp(field, "VICsiCh0WidthLSCnt") == 0) { + state->width_ls = value; + fields |= FIELD_ERROR_COUNTER; + } else if (strcmp(field, "VICsiCh0HeightGTCnt") == 0) { + state->height_gt = value; + fields |= FIELD_ERROR_COUNTER; + } else if (strcmp(field, "VICsiCh0HeightLSCnt") == 0) { + state->height_ls = value; + fields |= FIELD_ERROR_COUNTER; + } + } + return fields; +} + +inline ProcReadStatus read_proc_state(State *state, uint32_t *fields) +{ + if (fields != NULL) { + *fields = FIELD_NONE; + } + if (state == NULL) { + return PROC_READ_NO_KNOWN_FIELDS; + } + + FILE *fp = fopen("/proc/cvitek/vi_dbg", "r"); + if (fp == NULL) { + return PROC_READ_OPEN_FAILED; + } + + uint32_t parsed_fields = parse(fp, state); + fclose(fp); + if (fields != NULL) { + *fields = parsed_fields; + } + return parsed_fields == FIELD_NONE ? PROC_READ_NO_KNOWN_FIELDS : PROC_READ_OK; +} + +inline uint8_t classify(const State &state) +{ + if (state.dev_fps == 0) return 2; + if (state.fps != 0) return 1; + if (state.width_gt != 0) return 3; + if (state.width_ls != 0) return 4; + if (state.height_gt != 0) return 5; + if (state.height_ls != 0) return 6; + return 7; +} + +inline const char *read_status_name(ReadStatus status) +{ + switch (status) { + case READ_OK: + return "ok"; + case READ_UNAVAILABLE: + return "unavailable"; + case READ_INVALID: + return "invalid"; + case READ_BUSY: + return "busy"; + case READ_STALE: + return "stale"; + } + return "unknown"; +} + +namespace detail { + +enum : uint32_t { + SHARED_MAGIC = 0x4e4b5649U, + SHARED_VERSION = 1U, +}; + +struct SharedState { + uint32_t sequence; + uint32_t magic; + uint32_t version; + uint32_t updated_ms; + State state; +}; + +} // namespace detail + +inline ReadStatus read_state(State *state, uint32_t max_age_ms) +{ + if (state == NULL) { + return READ_INVALID; + } + + int fd = open(shared_path(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + if (fd < 0) { + return READ_UNAVAILABLE; + } + + struct stat stat_buf; + if (fstat(fd, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode) || + stat_buf.st_size < (off_t)sizeof(detail::SharedState)) { + close(fd); + return READ_INVALID; + } + + void *addr = mmap(NULL, sizeof(detail::SharedState), PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (addr == MAP_FAILED) { + return READ_UNAVAILABLE; + } + + const detail::SharedState *shared = (const detail::SharedState *)addr; + ReadStatus result = READ_BUSY; + for (int attempt = 0; attempt < 3; attempt++) { + uint32_t sequence = __atomic_load_n(&shared->sequence, __ATOMIC_ACQUIRE); + if (sequence & 1U) { + continue; + } + + uint32_t magic = __atomic_load_n(&shared->magic, __ATOMIC_RELAXED); + uint32_t version = __atomic_load_n(&shared->version, __ATOMIC_RELAXED); + uint32_t updated_ms = __atomic_load_n(&shared->updated_ms, __ATOMIC_RELAXED); + State value = {}; + value.dev_fps = __atomic_load_n(&shared->state.dev_fps, __ATOMIC_RELAXED); + value.fps = __atomic_load_n(&shared->state.fps, __ATOMIC_RELAXED); + value.width_gt = __atomic_load_n(&shared->state.width_gt, __ATOMIC_RELAXED); + value.width_ls = __atomic_load_n(&shared->state.width_ls, __ATOMIC_RELAXED); + value.height_gt = __atomic_load_n(&shared->state.height_gt, __ATOMIC_RELAXED); + value.height_ls = __atomic_load_n(&shared->state.height_ls, __ATOMIC_RELAXED); + __atomic_thread_fence(__ATOMIC_ACQUIRE); + uint32_t final_sequence = __atomic_load_n(&shared->sequence, __ATOMIC_RELAXED); + if (sequence != final_sequence || (final_sequence & 1U)) { + continue; + } + if (magic != detail::SHARED_MAGIC || version != detail::SHARED_VERSION) { + result = READ_INVALID; + break; + } + if (monotonic_ms() - updated_ms > max_age_ms) { + result = READ_STALE; + break; + } + *state = value; + result = READ_OK; + break; + } + + munmap(addr, sizeof(detail::SharedState)); + return result; +} + +} // namespace vi_state_shared + +#endif // VI_STATE_SHARED_HPP_ diff --git a/support/sg2002/additional/kvm/src/kvm_vision.cpp b/support/sg2002/additional/kvm/src/kvm_vision.cpp index 2127c20..f8e4570 100644 --- a/support/sg2002/additional/kvm/src/kvm_vision.cpp +++ b/support/sg2002/additional/kvm/src/kvm_vision.cpp @@ -10,7 +10,8 @@ * // free 错内存时会炸的问题 */ #include "kvm_vision.h" -#include "internal/vi_state_shared.hpp" +#include "vi_state_shared.hpp" +#include "internal/vi_state_writer.hpp" #define default_venc_chn 1 @@ -44,6 +45,7 @@ #define watchdog_mode_path "/etc/kvm/watchdog" #define watchdog_temp_path "/tmp/watchdog" #define watchdog_file "/tmp/nanokvm_wd" +#define vi_state_publish_interval_ms 10000U #define LT6911_ADDR 0x2B #define LT6911_READ 0xFF @@ -119,6 +121,8 @@ kvmv_data_t kvmv_data_buffer[kvmv_data_buffer_size]; uint8_t kvmv_data_buffer_index = 0; uint8_t debug_en = 0; +uint8_t last_vi_state_code = 0; +uint32_t last_vi_state_refresh_ms = 0; void debug(const char *format, ...) { if(debug_en){ @@ -126,6 +130,15 @@ void debug(const char *format, ...) } } +uint8_t refresh_vi_state() +{ + // The driver read blocks for about a second; mark the deadline before it so + // that the publication cadence measures wall-clock time, not read time plus it. + last_vi_state_refresh_ms = vi_state_shared::monotonic_ms(); + last_vi_state_code = vi_state_shared::refresh(); + return last_vi_state_code; +} + uint8_t to_roll(int8_t _input) { if(_input < 0) return _input + kvmv_data_buffer_size; @@ -224,14 +237,16 @@ int get_hdmi_mode(void) if(access(hdmi_mode_path, F_OK) == 0){ // exist FILE *fp; - int file_size; uint8_t tmp8; - uint8_t RW_Data[2]; + uint8_t RW_Data[3] = {0}; fp = fopen(hdmi_mode_path, "r"); - fread(RW_Data, sizeof(char), 1, fp); + if (fp == NULL) { + kvmv_cfg.hdmi_mode = 0; + return 0; + } + fread(RW_Data, sizeof(char), sizeof(RW_Data) - 1, fp); fclose(fp); - RW_Data[2] = 0; tmp8 = atoi((char*)RW_Data); if(tmp8 > 2) { tmp8 = 0; @@ -354,7 +369,7 @@ uint8_t auto_try_res() uint8_t auto_trying_times = 0; for (auto_trying_times = 0; auto_trying_times < sizeof(hdmi_res_list)/4; auto_trying_times++){ - err_code = vi_state_shared::refresh(); + err_code = refresh_vi_state(); switch(err_code){ case 0: // shouldn't be possible to run here @@ -369,6 +384,7 @@ uint8_t auto_try_res() // HDMI not detected or resolution not supported; interval checks will continue printf("[kvmv] Cannot obtain HDMI input\n"); auto_trying_times--; + time::sleep_ms(1000); break; case 3: // width too small case 4: // width too large @@ -393,9 +409,9 @@ uint8_t auto_try_res() break; } } - if (vi_state_shared::refresh() == 1) return 1; - if (vi_state_shared::refresh() == 2) return 2; - else return 0; + err_code = refresh_vi_state(); + if (err_code == 1 || err_code == 2) return err_code; + return 0; } /* return : @@ -1092,9 +1108,8 @@ void* vi_subsystem_detection(void * arg) get_hdmi_version(); - // while(!app::need_exit()) - uint8_t while_count_detect_res = 0; - uint8_t while_count_publish_vi_state = 0; + // Refresh on elapsed time, not loop iterations whose work varies widely. + last_vi_state_refresh_ms = vi_state_shared::monotonic_ms() - vi_state_publish_interval_ms; while(true) { if(kvmv_cfg.try_exit_thread == 1) @@ -1103,13 +1118,24 @@ void* vi_subsystem_detection(void * arg) uint8_t get_new_hdmi_mode = get_hdmi_mode(); uint8_t try_res; uint8_t err_code; + uint8_t vi_state_refreshed = 0; + uint8_t vi_state_due = vi_state_shared::monotonic_ms() - last_vi_state_refresh_ms >= vi_state_publish_interval_ms; + uint8_t manual_vi_init_now = + (kvmv_cfg.hdmi_mode == 2 && + (get_new_hdmi_mode == 1 || kvmv_cfg.vi_detect_state == 0)); + uint8_t defer_vi_state_refresh = + (kvmv_cfg.hdmi_mode == 1 && + (kvmv_cfg.vi_detect_state == 1 || get_new_hdmi_mode == 1)) || + (kvmv_cfg.hdmi_mode == 2 && + (manual_vi_init_now || kvmv_cfg.vi_detect_state == 1)); + + if (vi_state_due && !defer_vi_state_refresh) { + refresh_vi_state(); + vi_state_refreshed = 1; + } switch (kvmv_cfg.hdmi_mode){ case 0: - while_count_publish_vi_state = (while_count_publish_vi_state + 1)%100; - if (while_count_publish_vi_state == 1) { - vi_state_shared::refresh(); - } // Switching to Mode 0 requires restarting HDMI (effective only for PCIe version) // Handling of automatic detection situations if(get_new_hdmi_mode == 1){ @@ -1264,20 +1290,21 @@ void* vi_subsystem_detection(void * arg) } } else if (kvmv_cfg.vi_detect_state == 2){ // Low-frequency detection of HDMI status, no log output - printf("[kvmv] kvmv_cfg.vi_detect_state == 2\n"); - err_code = vi_state_shared::refresh(); - if (err_code != 1) { + if (vi_state_refreshed && last_vi_state_code != 1) { kvmv_cfg.vi_detect_state = 1; } - time::sleep_ms(1000); } else { kvmv_cfg.vi_detect_state = 1; } break; case 2: // Manually initialize VI. - while_count_detect_res = (while_count_detect_res + 1)%100; - if(while_count_detect_res == 1){ + if (manual_vi_init_now) { + // Initialize immediately when entering manual mode instead of + // waiting for the next periodic state refresh. + kvmv_cfg.vi_detect_state = 1; + } + if(manual_vi_init_now || vi_state_due){ if (kvmv_cfg.vi_detect_state == 1){ // detect_res if (get_manual_resolution()) { @@ -1285,8 +1312,8 @@ void* vi_subsystem_detection(void * arg) cam->restart(default_vpss_width, default_vpss_height, image::FMT_YVU420SP); } - // dbg info - err_code = vi_state_shared::refresh(); + // Sample after a manual resolution change. + err_code = refresh_vi_state(); switch(err_code){ case 0: debug("[kvmv] VI not init\n"); @@ -1316,7 +1343,7 @@ void* vi_subsystem_detection(void * arg) } } else if (kvmv_cfg.vi_detect_state == 2){ // detection of HDMI status, no log output - err_code = vi_state_shared::refresh(); + err_code = last_vi_state_code; if (err_code != 1) kvmv_cfg.vi_detect_state = 1; } else { kvmv_cfg.vi_detect_state = 1; diff --git a/support/sg2002/additional/kvm/src/vi_state_shared.cpp b/support/sg2002/additional/kvm/src/vi_state_shared.cpp index 3f8002c..98fc91c 100644 --- a/support/sg2002/additional/kvm/src/vi_state_shared.cpp +++ b/support/sg2002/additional/kvm/src/vi_state_shared.cpp @@ -1,55 +1,87 @@ -#include "internal/vi_state_shared.hpp" +#include "vi_state_shared.hpp" +#include "internal/vi_state_writer.hpp" -#include -#include +#include namespace vi_state_shared { namespace { -detail::SharedState *map_writer() +void log_once(bool *logged, const char *message) { - static detail::SharedState *shared = NULL; - if (shared != NULL) { - return shared; + if (!__atomic_exchange_n(logged, true, __ATOMIC_RELAXED)) { + fprintf(stderr, "[vi_state] %s\n", message); } - - int fd = open(detail::path, O_CREAT | O_RDWR | O_CLOEXEC, 0644); - if (fd < 0) { - return NULL; - } - if (ftruncate(fd, sizeof(*shared)) != 0) { - close(fd); - return NULL; - } - void *addr = mmap(NULL, sizeof(*shared), PROT_READ | PROT_WRITE, - MAP_SHARED, fd, 0); - close(fd); - if (addr == MAP_FAILED) { - return NULL; - } - shared = (detail::SharedState *)addr; - return shared; } -void publish(const State &state) +void reset_log_once(bool *logged) { - detail::SharedState *shared = map_writer(); - if (shared == NULL) { - return; + __atomic_store_n(logged, false, __ATOMIC_RELAXED); +} + +bool publish(const State &state) +{ + static bool logged_open_failed = false; + static bool logged_lock_failed = false; + static bool logged_file_failed = false; + static bool logged_size_failed = false; + static bool logged_map_failed = false; + + int fd = open(shared_path(), O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600); + if (fd < 0) { + log_once(&logged_open_failed, "cannot open shared state"); + return false; + } + if (flock(fd, LOCK_EX) != 0) { + log_once(&logged_lock_failed, "cannot lock shared state"); + close(fd); + return false; } - uint32_t sequence = __atomic_load_n(&shared->sequence, __ATOMIC_RELAXED) | 1U; - __atomic_store_n(&shared->sequence, sequence, __ATOMIC_SEQ_CST); - __atomic_store_n(&shared->magic, detail::magic, __ATOMIC_RELAXED); - __atomic_store_n(&shared->version, detail::version, __ATOMIC_RELAXED); - __atomic_store_n(&shared->updated_ms, detail::monotonic_ms(), __ATOMIC_RELAXED); + struct stat stat_buf; + if (fstat(fd, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode) || fchmod(fd, 0600) != 0) { + log_once(&logged_file_failed, "shared state is not a private regular file"); + close(fd); + return false; + } + if (ftruncate(fd, sizeof(detail::SharedState)) != 0) { + log_once(&logged_size_failed, "cannot size shared state"); + close(fd); + return false; + } + + void *addr = mmap(NULL, sizeof(detail::SharedState), PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + log_once(&logged_map_failed, "cannot map shared state"); + close(fd); + return false; + } + + detail::SharedState *shared = (detail::SharedState *)addr; + uint32_t sequence = __atomic_load_n(&shared->sequence, __ATOMIC_RELAXED); + sequence = (sequence & ~1U) + 1U; + __atomic_store_n(&shared->sequence, sequence, __ATOMIC_RELAXED); + __atomic_thread_fence(__ATOMIC_RELEASE); + __atomic_store_n(&shared->magic, detail::SHARED_MAGIC, __ATOMIC_RELAXED); + __atomic_store_n(&shared->version, detail::SHARED_VERSION, __ATOMIC_RELAXED); + __atomic_store_n(&shared->updated_ms, monotonic_ms(), __ATOMIC_RELAXED); __atomic_store_n(&shared->state.dev_fps, state.dev_fps, __ATOMIC_RELAXED); __atomic_store_n(&shared->state.fps, state.fps, __ATOMIC_RELAXED); __atomic_store_n(&shared->state.width_gt, state.width_gt, __ATOMIC_RELAXED); __atomic_store_n(&shared->state.width_ls, state.width_ls, __ATOMIC_RELAXED); __atomic_store_n(&shared->state.height_gt, state.height_gt, __ATOMIC_RELAXED); __atomic_store_n(&shared->state.height_ls, state.height_ls, __ATOMIC_RELAXED); - __atomic_store_n(&shared->sequence, sequence + 1U, __ATOMIC_RELEASE); + __atomic_thread_fence(__ATOMIC_RELEASE); + __atomic_store_n(&shared->sequence, sequence + 1U, __ATOMIC_RELAXED); + + munmap(addr, sizeof(detail::SharedState)); + close(fd); + reset_log_once(&logged_open_failed); + reset_log_once(&logged_lock_failed); + reset_log_once(&logged_file_failed); + reset_log_once(&logged_size_failed); + reset_log_once(&logged_map_failed); + return true; } } // namespace @@ -65,50 +97,36 @@ void publish(const State &state) */ uint8_t refresh() { - FILE *fp = fopen("/proc/cvitek/vi_dbg", "r"); - if (fp == NULL) { - return 0; - } + static bool logged_open_failed = false; + static bool logged_no_fields = false; + static bool logged_missing_fields = false; + static bool logged_publish_failed = false; State state = {}; - bool have_dev_fps = false; - bool have_fps = false; - char line[256]; - char field[32]; - unsigned int value; - while (fgets(line, sizeof(line), fp) != NULL) { - if (sscanf(line, "%31s : %u", field, &value) != 2) { - continue; - } - if (strcmp(field, "VIDevFPS") == 0) { - state.dev_fps = value; - have_dev_fps = true; - } else if (strcmp(field, "VIFPS") == 0) { - state.fps = value; - have_fps = true; - } else if (strcmp(field, "VICsiCh0WidthGTCnt") == 0) { - state.width_gt = value; - } else if (strcmp(field, "VICsiCh0WidthLSCnt") == 0) { - state.width_ls = value; - } else if (strcmp(field, "VICsiCh0HeightGTCnt") == 0) { - state.height_gt = value; - } else if (strcmp(field, "VICsiCh0HeightLSCnt") == 0) { - state.height_ls = value; - } - } - fclose(fp); - - if (!have_dev_fps || !have_fps) { + uint32_t fields = FIELD_NONE; + ProcReadStatus status = read_proc_state(&state, &fields); + if (status == PROC_READ_OPEN_FAILED) { + log_once(&logged_open_failed, "cannot open /proc/cvitek/vi_dbg"); return 0; } - publish(state); - if (state.dev_fps == 0) return 2; - if (state.fps != 0) return 1; - if (state.width_gt != 0) return 3; - if (state.width_ls != 0) return 4; - if (state.height_gt != 0) return 5; - if (state.height_ls != 0) return 6; - return 7; + if (status == PROC_READ_NO_KNOWN_FIELDS) { + log_once(&logged_no_fields, "no known fields in /proc/cvitek/vi_dbg"); + return 0; + } + if ((fields & (FIELD_DEV_FPS | FIELD_FPS)) != (FIELD_DEV_FPS | FIELD_FPS)) { + log_once(&logged_missing_fields, "missing VIDevFPS or VIFPS in /proc/cvitek/vi_dbg"); + return 0; + } + if (!publish(state)) { + log_once(&logged_publish_failed, "cannot publish VI state"); + return 0; + } + + reset_log_once(&logged_open_failed); + reset_log_once(&logged_no_fields); + reset_log_once(&logged_missing_fields); + reset_log_once(&logged_publish_failed); + return classify(state); } } // namespace vi_state_shared diff --git a/support/sg2002/kvm_system/main/include/config.h b/support/sg2002/kvm_system/main/include/config.h index 014c2c3..e4feb35 100644 --- a/support/sg2002/kvm_system/main/include/config.h +++ b/support/sg2002/kvm_system/main/include/config.h @@ -51,7 +51,7 @@ typedef struct { int8_t eth_state = -1; // cat /sys/class/net/eth0/carrier int8_t wifi_state = -1; // cat /sys/class/net/wlan0/carrier int8_t tail_state = -1; // ifconfig tailscale0 | grep 'inet addr' | awk '{print $2}' - int8_t hdmi_state = -1; // cat /proc/cvitek/vi_dbg | grep VIFPS | awk '{print $3}' (1s) + int8_t hdmi_state = -1; // Shared VI state, with a native fallback on failure int8_t usb_state = -1; // cat /sys/class/udc/4340000.usb/state int8_t hid_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GSn(n=012) int8_t rndis_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/rndis.usb0 @@ -84,7 +84,7 @@ typedef struct { int8_t eth_state = -1; // cat /sys/class/net/eth0/carrier int8_t wifi_state = -1; // cat /sys/class/net/wlan0/carrier int8_t tail_state = -1; // ifconfig tailscale0 | grep inet\ addr | awk '{print $2}' - int8_t hdmi_state = -1; // cat /proc/cvitek/vi_dbg | grep VIFPS | awk '{print $3}' (1s) + int8_t hdmi_state = -1; // Shared VI state, with a native fallback on failure int8_t usb_state = -1; // cat /sys/class/udc/4340000.usb/state int8_t hid_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GSn(n=012) int8_t rndis_state = -1; // (exist?) /sys/kernel/config/usb_gadget/g0/configs/c.1/rndis.usb0 diff --git a/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp index fbf6f01..f455f92 100644 --- a/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp +++ b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp @@ -1,6 +1,6 @@ #include "config.h" #include "system_state.h" -#include "internal/vi_state_shared.hpp" +#include "vi_state_shared.hpp" #include #include #include @@ -237,12 +237,66 @@ void kvm_update_usb_state() void kvm_update_hdmi_state() { + const uint32_t shared_state_ttl_ms = 30000U; + const uint32_t fallback_state_ttl_ms = 10000U; static uint8_t check_times = 4; + static vi_state_shared::State fallback_state = {}; + static uint32_t fallback_updated_ms = 0; + static uint32_t fallback_attempted_ms = 0; + static bool fallback_valid = false; + static bool fallback_attempted = false; + static bool shared_failure_logged = false; + static bool fallback_failure_logged = false; if(++check_times > 5){ check_times = 0; - vi_state_shared::State state; - if (vi_state_shared::read(&state, 3000U)) { + vi_state_shared::State state = {}; + vi_state_shared::ReadStatus status = vi_state_shared::read_state(&state, shared_state_ttl_ms); + if (status == vi_state_shared::READ_OK) { kvm_sys_state.hdmi_state = state.fps == 0 ? 0 : 1; + if (shared_failure_logged) { + fprintf(stderr, "[kvm_system] VI shared state recovered\n"); + } + shared_failure_logged = false; + fallback_failure_logged = false; + return; + } + + if (!shared_failure_logged) { + fprintf(stderr, "[kvm_system] VI shared state %s; using direct fallback\n", + vi_state_shared::read_status_name(status)); + shared_failure_logged = true; + } + + uint32_t now = vi_state_shared::monotonic_ms(); + if (!fallback_attempted || now - fallback_attempted_ms >= fallback_state_ttl_ms) { + fallback_attempted_ms = now; + fallback_attempted = true; + uint32_t fields = vi_state_shared::FIELD_NONE; + vi_state_shared::State direct_state = {}; + vi_state_shared::ProcReadStatus direct_status = + vi_state_shared::read_proc_state(&direct_state, &fields); + if (direct_status == vi_state_shared::PROC_READ_OK && + (fields & vi_state_shared::FIELD_FPS) != 0U) { + fallback_state = direct_state; + fallback_updated_ms = now; + fallback_valid = true; + if (fallback_failure_logged) { + fprintf(stderr, "[kvm_system] direct VI fallback recovered\n"); + } + fallback_failure_logged = false; + } else { + fallback_valid = false; + if (!fallback_failure_logged) { + fprintf(stderr, "[kvm_system] direct VI fallback unavailable\n"); + fallback_failure_logged = true; + } + } + } + + if (fallback_valid && now - fallback_updated_ms <= fallback_state_ttl_ms) { + kvm_sys_state.hdmi_state = fallback_state.fps == 0 ? 0 : 1; + } else { + kvm_sys_state.hdmi_state = -1; } } }