Issue pikvm/pikvm#1235: Fixed gadgets on UDC re-bind

After unbind and bind, the gadgets stop working,
unless we recreate their links in the profile.
Some kind of kernel bug.
This commit is contained in:
Maxim Devaev 2024-02-05 16:04:28 +02:00
parent d3f2b57fdc
commit af9c2f1f59
2 changed files with 56 additions and 12 deletions

View File

@ -57,31 +57,51 @@ class _GadgetControl:
try: try:
yield yield
finally: finally:
self.__recreate_profile()
time.sleep(self.__init_delay) time.sleep(self.__init_delay)
with open(udc_path, "w") as file: with open(udc_path, "w") as file:
file.write(udc) file.write(udc)
def __recreate_profile(self) -> None:
# XXX: See pikvm/pikvm#1235
# After unbind and bind, the gadgets stop working,
# unless we recreate their links in the profile.
# Some kind of kernel bug.
for func in os.listdir(self.__get_fdest_path()):
path = self.__get_fdest_path(func)
if os.path.islink(path):
try:
os.unlink(path)
os.symlink(self.__get_fsrc_path(func), path)
except (FileNotFoundError, FileExistsError):
pass
def __read_metas(self) -> Generator[dict, None, None]: def __read_metas(self) -> Generator[dict, None, None]:
for meta_name in sorted(os.listdir(self.__meta_path)): for meta_name in sorted(os.listdir(self.__meta_path)):
with open(os.path.join(self.__meta_path, meta_name)) as file: with open(os.path.join(self.__meta_path, meta_name)) as file:
yield json.loads(file.read()) yield json.loads(file.read())
def __get_fsrc_path(self, func: str) -> str:
return usb.get_gadget_path(self.__gadget, usb.G_FUNCTIONS, func)
def __get_fdest_path(self, func: (str | None)=None) -> str:
if func is None:
return usb.get_gadget_path(self.__gadget, usb.G_PROFILE)
return usb.get_gadget_path(self.__gadget, usb.G_PROFILE, func)
def enable_functions(self, funcs: list[str]) -> None: def enable_functions(self, funcs: list[str]) -> None:
with self.__udc_stopped(): with self.__udc_stopped():
for func in funcs: for func in funcs:
os.symlink( os.symlink(self.__get_fsrc_path(func), self.__get_fdest_path(func))
usb.get_gadget_path(self.__gadget, usb.G_FUNCTIONS, func),
usb.get_gadget_path(self.__gadget, usb.G_PROFILE, func),
)
def disable_functions(self, funcs: list[str]) -> None: def disable_functions(self, funcs: list[str]) -> None:
with self.__udc_stopped(): with self.__udc_stopped():
for func in funcs: for func in funcs:
os.unlink(usb.get_gadget_path(self.__gadget, usb.G_PROFILE, func)) os.unlink(self.__get_fdest_path(func))
def list_functions(self) -> None: def list_functions(self) -> None:
for meta in self.__read_metas(): for meta in self.__read_metas():
enabled = os.path.exists(usb.get_gadget_path(self.__gadget, usb.G_PROFILE, meta["func"])) enabled = os.path.exists(self.__get_fdest_path(meta["func"]))
print(f"{'+' if enabled else '-'} {meta['func']} # {meta['name']}") print(f"{'+' if enabled else '-'} {meta['func']} # {meta['name']}")
def make_gpio_config(self) -> None: def make_gpio_config(self) -> None:

View File

@ -105,29 +105,53 @@ class Plugin(BaseUserGpioDriver):
async def read(self, pin: str) -> bool: async def read(self, pin: str) -> bool:
if pin == "udc": if pin == "udc":
return self.__is_udc_enabled() return self.__is_udc_enabled()
return os.path.exists(os.path.join(self.__profile_path, pin)) return os.path.exists(self.__get_fdest_path(pin))
async def write(self, pin: str, state: bool) -> None: async def write(self, pin: str, state: bool) -> None:
async with self.__lock: async with self.__lock:
if self.read(pin) == state:
return
if pin == "udc": if pin == "udc":
if state:
self.__recreate_profile()
self.__set_udc_enabled(state) self.__set_udc_enabled(state)
else: else:
if self.__is_udc_enabled(): if self.__is_udc_enabled():
self.__set_udc_enabled(False) self.__set_udc_enabled(False)
try: try:
if state: if state:
os.symlink( os.symlink(self.__get_fsrc_path(pin), self.__get_fdest_path(pin))
os.path.join(self.__functions_path, pin),
os.path.join(self.__profile_path, pin),
)
else: else:
os.unlink(os.path.join(self.__profile_path, pin)) os.unlink(self.__get_fdest_path(pin))
except (FileNotFoundError, FileExistsError):
pass
finally: finally:
self.__recreate_profile()
try: try:
await asyncio.sleep(self.__init_delay) await asyncio.sleep(self.__init_delay)
finally: finally:
self.__set_udc_enabled(True) self.__set_udc_enabled(True)
def __recreate_profile(self) -> None:
# XXX: See pikvm/pikvm#1235
# After unbind and bind, the gadgets stop working,
# unless we recreate their links in the profile.
# Some kind of kernel bug.
for func in os.listdir(self.__profile_path):
path = self.__get_fdest_path(func)
if os.path.islink(path):
try:
os.unlink(path)
os.symlink(self.__get_fsrc_path(func), path)
except (FileNotFoundError, FileNotFoundError):
pass
def __get_fsrc_path(self, func: str) -> str:
return os.path.join(self.__functions_path, func)
def __get_fdest_path(self, func: str) -> str:
return os.path.join(self.__profile_path, func)
def __set_udc_enabled(self, enabled: bool) -> None: def __set_udc_enabled(self, enabled: bool) -> None:
with open(self.__udc_path, "w") as file: with open(self.__udc_path, "w") as file:
file.write(self.__udc if enabled else "\n") file.write(self.__udc if enabled else "\n")