mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-02-01 02:21:53 +08:00
fix: 修复适配全志平台 OTG 低端点情况
This commit is contained in:
@@ -168,6 +168,10 @@ pub enum OtgHidProfile {
|
||||
Full,
|
||||
/// Full HID device set without MSD
|
||||
FullNoMsd,
|
||||
/// Full HID device set without consumer control
|
||||
FullNoConsumer,
|
||||
/// Full HID device set without consumer control and MSD
|
||||
FullNoConsumerNoMsd,
|
||||
/// Legacy profile: only keyboard
|
||||
LegacyKeyboard,
|
||||
/// Legacy profile: only relative mouse
|
||||
@@ -203,6 +207,15 @@ impl OtgHidFunctions {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn full_no_consumer() -> Self {
|
||||
Self {
|
||||
keyboard: true,
|
||||
mouse_relative: true,
|
||||
mouse_absolute: true,
|
||||
consumer: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn legacy_keyboard() -> Self {
|
||||
Self {
|
||||
keyboard: true,
|
||||
@@ -237,6 +250,8 @@ impl OtgHidProfile {
|
||||
match self {
|
||||
Self::Full => OtgHidFunctions::full(),
|
||||
Self::FullNoMsd => OtgHidFunctions::full(),
|
||||
Self::FullNoConsumer => OtgHidFunctions::full_no_consumer(),
|
||||
Self::FullNoConsumerNoMsd => OtgHidFunctions::full_no_consumer(),
|
||||
Self::LegacyKeyboard => OtgHidFunctions::legacy_keyboard(),
|
||||
Self::LegacyMouseRelative => OtgHidFunctions::legacy_mouse_relative(),
|
||||
Self::Custom => custom.clone(),
|
||||
|
||||
15
src/main.rs
15
src/main.rs
@@ -16,7 +16,7 @@ use one_kvm::events::EventBus;
|
||||
use one_kvm::extensions::ExtensionManager;
|
||||
use one_kvm::hid::{HidBackendType, HidController};
|
||||
use one_kvm::msd::MsdController;
|
||||
use one_kvm::otg::OtgService;
|
||||
use one_kvm::otg::{configfs, OtgService};
|
||||
use one_kvm::rustdesk::RustDeskService;
|
||||
use one_kvm::state::AppState;
|
||||
use one_kvm::video::format::{PixelFormat, Resolution};
|
||||
@@ -312,6 +312,19 @@ async fn main() -> anyhow::Result<()> {
|
||||
let will_use_msd = config.msd.enabled;
|
||||
|
||||
if will_use_otg_hid {
|
||||
let mut hid_functions = config.hid.effective_otg_functions();
|
||||
if let Some(udc) = configfs::resolve_udc_name(config.hid.otg_udc.as_deref()) {
|
||||
if configfs::is_low_endpoint_udc(&udc) && hid_functions.consumer {
|
||||
tracing::warn!(
|
||||
"UDC {} has low endpoint resources, disabling consumer control",
|
||||
udc
|
||||
);
|
||||
hid_functions.consumer = false;
|
||||
}
|
||||
}
|
||||
if let Err(e) = otg_service.update_hid_functions(hid_functions).await {
|
||||
tracing::warn!("Failed to apply HID functions: {}", e);
|
||||
}
|
||||
if let Err(e) = otg_service.enable_hid().await {
|
||||
tracing::warn!("Failed to pre-enable HID: {}", e);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,23 @@ pub fn find_udc() -> Option<String> {
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Check if UDC is known to have low endpoint resources
|
||||
pub fn is_low_endpoint_udc(name: &str) -> bool {
|
||||
let name = name.to_ascii_lowercase();
|
||||
name.contains("musb") || name.contains("musb-hdrc")
|
||||
}
|
||||
|
||||
/// Resolve preferred UDC name if available, otherwise auto-detect
|
||||
pub fn resolve_udc_name(preferred: Option<&str>) -> Option<String> {
|
||||
if let Some(name) = preferred {
|
||||
let path = Path::new("/sys/class/udc").join(name);
|
||||
if path.exists() {
|
||||
return Some(name.to_string());
|
||||
}
|
||||
}
|
||||
find_udc()
|
||||
}
|
||||
|
||||
/// Write string content to a file
|
||||
///
|
||||
/// For sysfs files, this function appends a newline and flushes
|
||||
|
||||
@@ -6,9 +6,9 @@ use std::path::PathBuf;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use super::configfs::{
|
||||
create_dir, find_udc, is_configfs_available, remove_dir, write_file, CONFIGFS_PATH,
|
||||
DEFAULT_GADGET_NAME, DEFAULT_USB_BCD_DEVICE, DEFAULT_USB_PRODUCT_ID, DEFAULT_USB_VENDOR_ID,
|
||||
USB_BCD_USB,
|
||||
create_dir, create_symlink, find_udc, is_configfs_available, remove_dir, remove_file,
|
||||
write_file, CONFIGFS_PATH, DEFAULT_GADGET_NAME, DEFAULT_USB_BCD_DEVICE, DEFAULT_USB_PRODUCT_ID,
|
||||
DEFAULT_USB_VENDOR_ID, USB_BCD_USB,
|
||||
};
|
||||
use super::endpoint::{EndpointAllocator, DEFAULT_MAX_ENDPOINTS};
|
||||
use super::function::{FunctionMeta, GadgetFunction};
|
||||
@@ -16,6 +16,8 @@ use super::hid::HidFunction;
|
||||
use super::msd::MsdFunction;
|
||||
use crate::error::{AppError, Result};
|
||||
|
||||
const REBIND_DELAY_MS: u64 = 300;
|
||||
|
||||
/// USB Gadget device descriptor configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GadgetDescriptor {
|
||||
@@ -249,9 +251,15 @@ impl OtgGadgetManager {
|
||||
AppError::Internal("No USB Device Controller (UDC) found".to_string())
|
||||
})?;
|
||||
|
||||
// Recreate config symlinks before binding to avoid kernel gadget issues after rebind
|
||||
if let Err(e) = self.recreate_config_links() {
|
||||
warn!("Failed to recreate gadget config links before bind: {}", e);
|
||||
}
|
||||
|
||||
info!("Binding gadget to UDC: {}", udc);
|
||||
write_file(&self.gadget_path.join("UDC"), &udc)?;
|
||||
self.bound_udc = Some(udc);
|
||||
std::thread::sleep(std::time::Duration::from_millis(REBIND_DELAY_MS));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -262,6 +270,7 @@ impl OtgGadgetManager {
|
||||
write_file(&self.gadget_path.join("UDC"), "")?;
|
||||
self.bound_udc = None;
|
||||
info!("Unbound gadget from UDC");
|
||||
std::thread::sleep(std::time::Duration::from_millis(REBIND_DELAY_MS));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -382,6 +391,47 @@ impl OtgGadgetManager {
|
||||
pub fn gadget_path(&self) -> &PathBuf {
|
||||
&self.gadget_path
|
||||
}
|
||||
|
||||
/// Recreate config symlinks from functions directory
|
||||
fn recreate_config_links(&self) -> Result<()> {
|
||||
let functions_path = self.gadget_path.join("functions");
|
||||
if !functions_path.exists() || !self.config_path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let entries = std::fs::read_dir(&functions_path).map_err(|e| {
|
||||
AppError::Internal(format!(
|
||||
"Failed to read functions directory {}: {}",
|
||||
functions_path.display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name = match name.to_str() {
|
||||
Some(n) => n,
|
||||
None => continue,
|
||||
};
|
||||
if !name.contains(".usb") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let src = functions_path.join(name);
|
||||
let dest = self.config_path.join(name);
|
||||
|
||||
if dest.exists() {
|
||||
if let Err(e) = remove_file(&dest) {
|
||||
warn!("Failed to remove existing config link {}: {}", dest.display(), e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
create_symlink(&src, &dest)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OtgGadgetManager {
|
||||
|
||||
@@ -187,7 +187,23 @@ pub async fn apply_hid_config(
|
||||
// 检查 OTG 描述符是否变更
|
||||
let descriptor_changed = old_config.otg_descriptor != new_config.otg_descriptor;
|
||||
let old_hid_functions = old_config.effective_otg_functions();
|
||||
let new_hid_functions = new_config.effective_otg_functions();
|
||||
let mut new_hid_functions = new_config.effective_otg_functions();
|
||||
|
||||
// Low-endpoint UDCs (e.g., musb) cannot handle consumer control endpoints reliably
|
||||
if new_config.backend == HidBackend::Otg {
|
||||
if let Some(udc) =
|
||||
crate::otg::configfs::resolve_udc_name(new_config.otg_udc.as_deref())
|
||||
{
|
||||
if crate::otg::configfs::is_low_endpoint_udc(&udc) && new_hid_functions.consumer {
|
||||
tracing::warn!(
|
||||
"UDC {} has low endpoint resources, disabling consumer control",
|
||||
udc
|
||||
);
|
||||
new_hid_functions.consumer = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hid_functions_changed = old_hid_functions != new_hid_functions;
|
||||
|
||||
if new_config.backend == HidBackend::Otg && new_hid_functions.is_empty() {
|
||||
|
||||
@@ -521,11 +521,39 @@ pub struct SetupRequest {
|
||||
pub hid_ch9329_port: Option<String>,
|
||||
pub hid_ch9329_baudrate: Option<u32>,
|
||||
pub hid_otg_udc: Option<String>,
|
||||
pub hid_otg_profile: Option<String>,
|
||||
// Extension settings
|
||||
pub ttyd_enabled: Option<bool>,
|
||||
pub rustdesk_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
fn normalize_otg_profile_for_low_endpoint(config: &mut AppConfig) {
|
||||
if !matches!(config.hid.backend, crate::config::HidBackend::Otg) {
|
||||
return;
|
||||
}
|
||||
let udc = crate::otg::configfs::resolve_udc_name(config.hid.otg_udc.as_deref());
|
||||
let Some(udc) = udc else {
|
||||
return;
|
||||
};
|
||||
if !crate::otg::configfs::is_low_endpoint_udc(&udc) {
|
||||
return;
|
||||
}
|
||||
match config.hid.otg_profile {
|
||||
crate::config::OtgHidProfile::Full => {
|
||||
config.hid.otg_profile = crate::config::OtgHidProfile::FullNoConsumer;
|
||||
}
|
||||
crate::config::OtgHidProfile::FullNoMsd => {
|
||||
config.hid.otg_profile = crate::config::OtgHidProfile::FullNoConsumerNoMsd;
|
||||
}
|
||||
crate::config::OtgHidProfile::Custom => {
|
||||
if config.hid.otg_functions.consumer {
|
||||
config.hid.otg_functions.consumer = false;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setup_init(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<SetupRequest>,
|
||||
@@ -601,6 +629,33 @@ pub async fn setup_init(
|
||||
if let Some(udc) = req.hid_otg_udc.clone() {
|
||||
config.hid.otg_udc = Some(udc);
|
||||
}
|
||||
if let Some(profile) = req.hid_otg_profile.clone() {
|
||||
config.hid.otg_profile = match profile.as_str() {
|
||||
"full" => crate::config::OtgHidProfile::Full,
|
||||
"full_no_msd" => crate::config::OtgHidProfile::FullNoMsd,
|
||||
"full_no_consumer" => crate::config::OtgHidProfile::FullNoConsumer,
|
||||
"full_no_consumer_no_msd" => crate::config::OtgHidProfile::FullNoConsumerNoMsd,
|
||||
"legacy_keyboard" => crate::config::OtgHidProfile::LegacyKeyboard,
|
||||
"legacy_mouse_relative" => crate::config::OtgHidProfile::LegacyMouseRelative,
|
||||
"custom" => crate::config::OtgHidProfile::Custom,
|
||||
_ => config.hid.otg_profile.clone(),
|
||||
};
|
||||
if matches!(config.hid.backend, crate::config::HidBackend::Otg) {
|
||||
match config.hid.otg_profile {
|
||||
crate::config::OtgHidProfile::Full
|
||||
| crate::config::OtgHidProfile::FullNoConsumer => {
|
||||
config.msd.enabled = true;
|
||||
}
|
||||
crate::config::OtgHidProfile::FullNoMsd
|
||||
| crate::config::OtgHidProfile::FullNoConsumerNoMsd
|
||||
| crate::config::OtgHidProfile::LegacyKeyboard
|
||||
| crate::config::OtgHidProfile::LegacyMouseRelative => {
|
||||
config.msd.enabled = false;
|
||||
}
|
||||
crate::config::OtgHidProfile::Custom => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extension settings
|
||||
if let Some(enabled) = req.ttyd_enabled {
|
||||
@@ -609,12 +664,32 @@ pub async fn setup_init(
|
||||
if let Some(enabled) = req.rustdesk_enabled {
|
||||
config.rustdesk.enabled = enabled;
|
||||
}
|
||||
|
||||
normalize_otg_profile_for_low_endpoint(config);
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Get updated config for HID reload
|
||||
let new_config = state.config.get();
|
||||
|
||||
if matches!(new_config.hid.backend, crate::config::HidBackend::Otg) {
|
||||
let mut hid_functions = new_config.hid.effective_otg_functions();
|
||||
if let Some(udc) =
|
||||
crate::otg::configfs::resolve_udc_name(new_config.hid.otg_udc.as_deref())
|
||||
{
|
||||
if crate::otg::configfs::is_low_endpoint_udc(&udc) && hid_functions.consumer {
|
||||
tracing::warn!(
|
||||
"UDC {} has low endpoint resources, disabling consumer control",
|
||||
udc
|
||||
);
|
||||
hid_functions.consumer = false;
|
||||
}
|
||||
}
|
||||
if let Err(e) = state.otg_service.update_hid_functions(hid_functions).await {
|
||||
tracing::warn!("Failed to apply HID functions during setup: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Extension config after save: ttyd.enabled={}, rustdesk.enabled={}",
|
||||
new_config.extensions.ttyd.enabled,
|
||||
@@ -727,6 +802,9 @@ pub async fn update_config(
|
||||
let new_config: AppConfig = serde_json::from_value(merged)
|
||||
.map_err(|e| AppError::BadRequest(format!("Invalid config format: {}", e)))?;
|
||||
|
||||
let mut new_config = new_config;
|
||||
normalize_otg_profile_for_low_endpoint(&mut new_config);
|
||||
|
||||
// Apply the validated config
|
||||
state.config.set(new_config.clone()).await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user