mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
fix(sg2002): harden VI state sharing
This commit is contained in:
Binary file not shown.
@@ -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_
|
||||
@@ -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_
|
||||
228
support/sg2002/additional/kvm/include/vi_state_shared.hpp
Normal file
228
support/sg2002/additional/kvm/include/vi_state_shared.hpp
Normal 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_
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user