fix(sg2002): harden VI state sharing

This commit is contained in:
watermeko
2026-07-30 01:40:43 +00:00
committed by Guoguo
parent 774d314ce9
commit 004fd59093
8 changed files with 443 additions and 211 deletions

Binary file not shown.

View File

@@ -1,107 +0,0 @@
#ifndef VI_STATE_SHARED_HPP_
#define VI_STATE_SHARED_HPP_
#include <fcntl.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
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_

View File

@@ -0,0 +1,12 @@
#ifndef VI_STATE_WRITER_HPP_
#define VI_STATE_WRITER_HPP_
#include <stdint.h>
namespace vi_state_shared {
uint8_t refresh();
} // namespace vi_state_shared
#endif // VI_STATE_WRITER_HPP_

View File

@@ -0,0 +1,228 @@
#ifndef VI_STATE_SHARED_HPP_
#define VI_STATE_SHARED_HPP_
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
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_

View File

@@ -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;

View File

@@ -1,55 +1,87 @@
#include "internal/vi_state_shared.hpp"
#include "vi_state_shared.hpp"
#include "internal/vi_state_writer.hpp"
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
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

View File

@@ -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

View File

@@ -1,6 +1,6 @@
#include "config.h"
#include "system_state.h"
#include "internal/vi_state_shared.hpp"
#include "vi_state_shared.hpp"
#include <sys/socket.h>
#include <net/if.h>
#include <sys/ioctl.h>
@@ -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;
}
}
}