Skip to content

Commit

Permalink
Use usb-utils for find device path and usb reset
Browse files Browse the repository at this point in the history
  • Loading branch information
knro committed Jan 9, 2025
1 parent af291d0 commit ed42e77
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 235 deletions.
13 changes: 7 additions & 6 deletions indi-asi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ endif()
set(indi_asi_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/asi_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/asi_ccd.cpp
${CMAKE_CURRENT_SOURCE_DIR}/usb_utils.cpp
)

add_executable(indi_asi_ccd ${indi_asi_SRCS})
Expand All @@ -50,6 +51,7 @@ endif()
set(indi_asi_single_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/asi_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/asi_single_ccd.cpp
${CMAKE_CURRENT_SOURCE_DIR}/usb_utils.cpp
)

add_executable(indi_asi_single_ccd ${indi_asi_single_SRCS})
Expand Down Expand Up @@ -124,16 +126,15 @@ install(TARGETS indi_asi_rotator RUNTIME DESTINATION bin)
install(TARGETS asi_camera_test RUNTIME DESTINATION bin)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/indi_asi.xml DESTINATION ${INDI_DATA_DIR})

########### get_asi_serials ###########
add_executable(get_asi_serials ${CMAKE_CURRENT_SOURCE_DIR}/get_asi_serials.cpp)
########### test-usb-utils ###########
add_executable(test-usb-utils ${CMAKE_CURRENT_SOURCE_DIR}/test-usb-utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/usb_utils.cpp)
IF (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "-framework IOKit -framework CoreFoundation")
target_link_libraries(get_asi_serials ${ASI_LIBRARIES} ${LIBUSB_LIBRARIES})
target_link_libraries(test-usb-utils ${LIBUSB_LIBRARIES})
ELSE()
target_link_libraries(get_asi_serials ${ASI_LIBRARIES} ${USB1_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} usb-1.0)
target_link_libraries(test-usb-utils ${INDI_LIBRARIES} ${USB1_LIBRARIES} usb-1.0)
ENDIF()

if (CMAKE_SYSTEM_NAME MATCHES "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm*")
target_link_libraries(get_asi_serials rt)
target_link_libraries(test-usb-utils rt)
endif()

139 changes: 8 additions & 131 deletions indi-asi/asi_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "asi_base.h"
#include "asi_helpers.h"
#include "usb_utils.h"

#include "config.h"

Expand Down Expand Up @@ -277,6 +278,7 @@ void ASIBase::workerExposure(const std::atomic_bool &isAboutToQuit, float durati
ASIStopExposure(mCameraInfo.CameraID);
ASICloseCamera(mCameraInfo.CameraID);

LOGF_INFO("Attempting USB reset for device %s...", mCameraInfo.Name);
resetUSBDevice();

LOG_INFO("Reopening camera after reset...");
Expand Down Expand Up @@ -1633,143 +1635,18 @@ void ASIBase::addFITSKeywords(INDI::CCDChip *targetChip, std::vector<INDI::FITSR

void ASIBase::resetUSBDevice()
{
char cmd[512];
char path[256] = {0};
LOGF_INFO("Finding USB port for device %04x:%04x...", 0x03c3, mCameraInfo.CameraID);

// Find the device's USB port path
snprintf(cmd, sizeof(cmd),
"for dev in /sys/bus/usb/devices/*; do "
" if [ -f \"$dev/idVendor\" ] && [ -f \"$dev/idProduct\" ]; then "
" if [ \"$(cat $dev/idVendor)\" = \"%04x\" ] && [ \"$(cat $dev/idProduct)\" = \"%04x\" ]; then "
" echo \"$dev\"; "
" exit 0; "
" fi; "
" fi; "
"done",
0x03c3, mCameraInfo.CameraID);

FILE *fp = popen(cmd, "r");
if (!fp)
{
LOGF_ERROR("Failed to execute device search: %s", strerror(errno));
return;
}

if (fgets(path, sizeof(path), fp) == NULL)
{
LOG_ERROR("Failed to find device path");
pclose(fp);
return;
}
pclose(fp);

// Remove newline if present
char *newline = strchr(path, '\n');
if (newline)
*newline = '\0';

LOGF_DEBUG("Found device at: %s", path);

// First try to unbind the device
LOG_INFO("Unbinding USB device...");
char unbind_path[512];
snprintf(unbind_path, sizeof(unbind_path), "%s/driver/unbind", path);
LOGF_INFO("Finding USB port for device %s...", mCameraInfo.Name);

// Get the device name (last part of path)
char *device_name = strrchr(path, '/');
if (device_name)
device_name++; // Skip the '/'
else
device_name = path;

FILE *unbind_fp = fopen(unbind_path, "w");
if (!unbind_fp)
{
LOGF_ERROR("Failed to open unbind path: %s", strerror(errno));
return;
}
fprintf(unbind_fp, "%s\n", device_name);
fclose(unbind_fp);
LOG_INFO("Device unbound");
usleep(1000000); // 1 second

// Try to reset the parent hub port
char parent_path[512];
snprintf(parent_path, sizeof(parent_path), "%s/..", path);
char real_parent[512];
if (realpath(parent_path, real_parent))
// Use shorter delays for camera reset to minimize downtime
// 500ms unbind wait, 1s suspend, 2s rediscover
if (USBUtils::resetDevice(0x03c3, mCameraInfo.Name, getDeviceName(), 500000, 1000000, 2000000))
{
LOGF_DEBUG("Found parent hub: %s", real_parent);

// Try port power control
char port_power[512];
snprintf(port_power, sizeof(port_power), "%s/power/level", real_parent);
if (access(port_power, W_OK) == 0)
{
LOG_INFO("Cycling parent hub port power...");
FILE *power_fp = fopen(port_power, "w");
if (!power_fp)
{
LOGF_ERROR("Failed to open power control: %s", strerror(errno));
}
else
{
fprintf(power_fp, "suspend\n");
fclose(power_fp);
usleep(2000000); // 2 seconds

power_fp = fopen(port_power, "w");
if (!power_fp)
{
LOGF_ERROR("Failed to reopen power control: %s", strerror(errno));
}
else
{
fprintf(power_fp, "on\n");
fclose(power_fp);
}
}
}
else
{
LOG_ERROR("No write access to power control");
}
LOG_INFO("USB port power cycle complete");
}
else
{
LOGF_ERROR("Failed to resolve parent hub path: %s", strerror(errno));
LOG_ERROR("Failed to reset USB device");
}

// Now rebind the device
LOG_INFO("Rebinding USB device...");
char bind_path[512];
snprintf(bind_path, sizeof(bind_path), "%s/../bind", unbind_path);
FILE *bind_fp = fopen(bind_path, "w");
if (!bind_fp)
{
// If direct bind fails, try the generic USB driver path
snprintf(bind_path, sizeof(bind_path), "/sys/bus/usb/drivers/usb/bind");
bind_fp = fopen(bind_path, "w");
if (!bind_fp)
{
LOGF_ERROR("Failed to open bind path: %s", strerror(errno));
// Continue anyway as the device might rebind automatically
}
}

if (bind_fp)
{
fprintf(bind_fp, "%s\n", device_name);
fclose(bind_fp);
LOG_INFO("Device rebound");
}

// Wait for device to be rediscovered
LOG_INFO("Waiting for device to be rediscovered...");
usleep(5000000); // 5 seconds

LOG_INFO("USB port power cycle complete");
}

bool ASIBase::saveConfigItems(FILE *fp)
Expand Down
98 changes: 0 additions & 98 deletions indi-asi/get_asi_serials.cpp

This file was deleted.

84 changes: 84 additions & 0 deletions indi-asi/test-usb-utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
SPDX-FileCopyrightText: 2025 Jasem Mutlaq <[email protected]>
SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "usb_utils.h"
#include <stdio.h>
#include <string.h>

void printUsage(const char *progName)
{
printf("Usage: %s <vendorID> <productName> [unbindWait powerSuspend rediscoverWait]\n", progName);
printf("Example: %s 0x03c3 \"ZWO ASI120MC-S\"\n", progName);
printf("Optional delays (microseconds):\n");
printf(" unbindWait: delay after unbinding (default: 1000000)\n");
printf(" powerSuspend: time in suspend state (default: 2000000)\n");
printf(" rediscoverWait: time to wait for rediscovery (default: 5000000)\n");
}

void testDevice(uint16_t vendorId, const char *productName, int unbindWait, int powerSuspend, int rediscoverWait)
{
printf("\nTesting device: VID=0x%04x Product='%s'\n", vendorId, productName);
printf("Using delays (microseconds):\n");
printf(" Unbind wait: %d\n", unbindWait);
printf(" Power suspend: %d\n", powerSuspend);
printf(" Rediscover wait: %d\n", rediscoverWait);

std::string path = USBUtils::findDevicePath(vendorId, productName, nullptr);
if (!path.empty())
{
printf("Found device path: %s\n", path.c_str());

printf("Testing USB reset...\n");
if (USBUtils::resetDevice(vendorId, productName, nullptr, unbindWait, powerSuspend, rediscoverWait))
{
printf("USB reset successful\n");
}
else
{
printf("USB reset failed\n");
}
}
else
{
printf("Device not found\n");
}
}

int main(int argc, char *argv[])
{
if (argc != 3 && argc != 6)
{
printUsage(argv[0]);
return 1;
}

uint16_t vendorId;
if (sscanf(argv[1], "0x%hx", &vendorId) != 1)
{
printf("Invalid vendor ID format. Use hex format like 0x03c3\n");
return 1;
}

int unbindWait = 1000000; // Default: 1 second
int powerSuspend = 2000000; // Default: 2 seconds
int rediscoverWait = 5000000; // Default: 5 seconds

if (argc == 6)
{
unbindWait = atoi(argv[3]);
powerSuspend = atoi(argv[4]);
rediscoverWait = atoi(argv[5]);

if (unbindWait <= 0 || powerSuspend <= 0 || rediscoverWait <= 0)
{
printf("Invalid delay values. All delays must be positive.\n");
return 1;
}
}

testDevice(vendorId, argv[2], unbindWait, powerSuspend, rediscoverWait);
return 0;
}
Loading

0 comments on commit ed42e77

Please sign in to comment.