Невозможно запросить интерфейс USB с помощью C + libusb в Mac OS X

У меня есть составное устройство USB + CDC, которое я построил с помощью микроконтроллера PIC32, и я пытаюсь подключиться к устройству и отправить некоторые данные в конечную точку интерфейса данных CDC с моего Mac.

Я знаю, что схема работает на 100%, поскольку устройство регистрируется как HID-джойстик, и я могу подключиться к устройству с помощью терминала Zoc в /dev/tty.usbmodemfa132. Я могу отправлять команды с помощью Zoc и видеть, как мой MCU отвечает на эти команды, мигая некоторыми светодиодами на схеме. .

Я запускаю это на Mac OS X Mavericks, но у меня была та же проблема с аналогичным примером, от которого я отказался несколько недель назад на Mountain Lion.

Мой код выглядит следующим образом:

// Includes -----------------------------------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>
#include <unistd.h>

// Defines ------------------------------------------------------------------------------------------------------------
#define VID 0x04d8
#define PID 0x005e
#define CDC_DATA_INTERFACE_ID 2

// Function Declarations ----------------------------------------------------------------------------------------------
void print_device(libusb_device *device);
void send(libusb_context *usb_context, uint16_t vid, uint16_t pid);

// Function Definitions -----------------------------------------------------------------------------------------------

/**
 * main
 */
int main(int argc, char **argv)
{
    libusb_device **usb_devices = NULL;
    libusb_context *usb_context = NULL;
    ssize_t device_count = 0;
    bool debug_enabled = false;
    int c;

    // Collect command line attributes
    while ( (c = getopt(argc, argv, "d")) != -1) {
        switch (c) {
            case 'd':
                debug_enabled = true;
                break;
        }
    }

    // Initialize USB context
    int result = libusb_init(&usb_context);
    if(result < 0) {
        printf("Unable to initialise libusb!");
        return EXIT_FAILURE;
    }

    // Turn debug mode on/off
    if(debug_enabled) {
        libusb_set_debug(usb_context, 3);
    }

    // Get USB device list
    device_count = libusb_get_device_list(usb_context, &usb_devices);
    if(device_count < 0) {
        puts("Unable to retrieve USB device list!");
    }

    // Iterate and print devices
    puts("VID    PID     Manufacturer Name\n------ ------ -------------------");
    for (int i = 0; i < device_count; i++) {
        print_device(usb_devices[i]);
    }

    // Attempt to send data
    send(usb_context, VID, PID);

    // Cleanup and exit
    libusb_free_device_list(usb_devices, 1);
    libusb_exit(usb_context);
    return EXIT_SUCCESS;
}

/**
 * print_device
 */
void print_device(libusb_device *device)
{
    struct libusb_device_descriptor device_descriptor;
    struct libusb_device_handle *device_handle = NULL;

    // Get USB device descriptor
    int result = libusb_get_device_descriptor(device, &device_descriptor);
    if (result < 0) {
        printf("Failed to get device descriptor!");
    }

    // Only print our devices
    if(VID == device_descriptor.idVendor && PID == device_descriptor.idProduct) {
        // Print VID & PID
        printf("0x%04x 0x%04x", device_descriptor.idVendor, device_descriptor.idProduct);
    } else {
        return;
    }

    // Attempt to open the device
    int open_result = libusb_open(device, &device_handle);
    if (open_result < 0) {
        libusb_close(device_handle);
        return;
    }

    // Print the device manufacturer string
    char manufacturer[256] = " ";
    if (device_descriptor.iManufacturer) {
        libusb_get_string_descriptor_ascii(device_handle, device_descriptor.iManufacturer,
            (unsigned char *)manufacturer, sizeof(manufacturer));
        printf(" %s", manufacturer);
    }

    puts("");

    libusb_close(device_handle);
}

/**
 * send
 */
void send(libusb_context *usb_context, uint16_t vid, uint16_t pid)
{
    libusb_device_handle *device_handle;
    device_handle = libusb_open_device_with_vid_pid(usb_context, vid, pid);

    if (device_handle == NULL) {
        puts("Unable to open device by VID & PID!");
        return;
    }
    puts("Device successfully opened");

    unsigned char *data = (unsigned char *)"test";

    if (libusb_kernel_driver_active(device_handle, CDC_DATA_INTERFACE_ID)) {
        puts("Kernel driver active");
        if (libusb_detach_kernel_driver(device_handle, CDC_DATA_INTERFACE_ID)) {
            puts("Kernel driver detached");
        }
    } else {
        puts("Kernel driver doesn't appear to be active");
    }

    int result = libusb_claim_interface(device_handle, CDC_DATA_INTERFACE_ID);
    if (result < 0) {
        puts("Unable to claim interface!");
        libusb_close(device_handle);
        return;
    }
    puts("Interface claimed");

    int written = 0;
    result = libusb_bulk_transfer(device_handle, (3 | LIBUSB_ENDPOINT_OUT), data, 4, &written, 0);
    if (result == 0 && written == 4) {
        puts("Send success");
    } else {
        puts("Send failed!");
    }

    result = libusb_release_interface(device_handle, CDC_DATA_INTERFACE_ID);
    if (result != 0) {
        puts("Unable to release interface!");
    }

    libusb_close(device_handle);
}

Я получаю следующий вывод ошибки:

libusb: 0.828223 error [darwin_open] USBDeviceOpen: another process has device opened for exclusive access
libusb: 0.828241 info [darwin_open] device open for access
Device successfully opened
Kernel driver doesn't appear to be active
libusb: 0.828641 error [darwin_claim_interface] USBInterfaceOpen: another process has device opened for exclusive access
Unable to claim interface!
libusb: 0.828766 info [event_thread_main] thread exiting

Есть ли способ освободить USB-устройство от другого процесса, освободив его, чтобы я мог потребовать его?

Есть ли альтернативный способ подключения к /dev/tty.usbmodemfa132 для отправки и получения данных через интерфейс CDC на USB-устройстве?

Возможно, альтернатива libusb?


person josef.van.niekerk    schedule 27.11.2013    source источник
comment
Попробуйте printf("Unable to claim interface: %s\n", libusb_error_name(result));, чтобы получить дополнительную информацию об ошибке.   -  person Étienne    schedule 28.11.2013
comment
Я думаю, что слышал кое-что о том, что у OSX есть такая проблема и с другими типами составных устройств - например, программисты, у которых также есть последовательный канал CDC.   -  person Chris Stratton    schedule 28.11.2013
comment
Будет ли проще найти другой способ отправки данных? Как такой терминал, как zoc, подключается к /dev/tty.usbmodema123? Бьюсь об заклад, он не использует libusb.   -  person josef.van.niekerk    schedule 28.11.2013
comment
Похоже, что единственным решением для Mac будет использование библиотеки termios в C для прямого подключения к /dev/tty...   -  person josef.van.niekerk    schedule 28.11.2013


Ответы (3)


Верно. Хотя libusb кажется всемогущим в Linux, вы не можете использовать его для подключения к интерфейсу USB CDC в Mac OS X, потому что этот интерфейс уже используется драйвером AppleUSBCDCACM.

Вам следует использовать стандартный способ подключения к последовательным портам. Это будет проще, потому что вам не нужно беспокоиться о конечных точках, массовых передачах и тому подобном. Вот пример кроссплатформенного кода на C, который я написал для одного из наших продуктов на основе CDC, который подключается к COM-порту для чтения и записи некоторых данных (источник). Он использует стандартные функции open, read и write.

// Uses POSIX functions to send and receive data from a Maestro.
// NOTE: You must change the 'const char * device' line below.

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#ifdef _WIN32
#define O_NOCTTY 0
#else
#include <termios.h>
#endif

// Gets the position of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
int maestroGetPosition(int fd, unsigned char channel)
{
  unsigned char command[] = {0x90, channel};
  if(write(fd, command, sizeof(command)) == -1)
  {
    perror("error writing");
    return -1;
  }

  unsigned char response[2];
  if(read(fd,response,2) != 2)
  {
    perror("error reading");
    return -1;
  }

  return response[0] + 256*response[1];
}

// Sets the target of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
// The units of 'target' are quarter-microseconds.
int maestroSetTarget(int fd, unsigned char channel, unsigned short target)
{
  unsigned char command[] = {0x84, channel, target & 0x7F, target >> 7 & 0x7F};
  if (write(fd, command, sizeof(command)) == -1)
  {
    perror("error writing");
    return -1;
  }
  return 0;
}

int main()
{
  // Open the Maestro's virtual COM port.
  const char * device = "\\\\.\\USBSER000";  // Windows, "\\\\.\\COM6" also works
  //const char * device = "/dev/ttyACM0";  // Linux
  //const char * device = "/dev/cu.usbmodem00034567"; // Mac OS X
  int fd = open(device, O_RDWR | O_NOCTTY);
  if (fd == -1)
  {
    perror(device);
    return 1;
  }

#ifndef _WIN32
  struct termios options;
  tcgetattr(fd, &options);
  options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  options.c_oflag &= ~(ONLCR | OCRNL);
  tcsetattr(fd, TCSANOW, &options);
#endif

  int position = maestroGetPosition(fd, 0);
  printf("Current position is %d.\n", position); 

  int target = (position < 6000) ? 7000 : 5000;
  printf("Setting target to %d (%d us).\n", target, target/4);
  maestroSetTarget(fd, 0, target);

  close(fd);
  return 0;
}
person David Grayson    schedule 28.11.2013
comment
Вау, это намного больше, чем я хотел. Спасибо, Дэвид! Я начал пытаться понять, могу ли я использовать kextunload для уничтожения драйверов USB CDC на Mac, но я думаю, что таким образом усложняю решение. - person josef.van.niekerk; 28.11.2013
comment
Я принимаю ваш ответ, хотя я не использовал ваш код. Это было очень полезно и направило меня на правильный путь. Я взял фрагмент C из en.wikibooks.org/wiki/Serial_Programming/Serial_Linux#termios с небольшими изменениями. Мою версию можно увидеть по адресу gist.github.com/josefvanniekerk/7702279. - person josef.van.niekerk; 29.11.2013
comment
поскольку принятый ответ больше похож на обходной путь, чем на ответ на вопрос, можно ли наконец сделать это с помощью libusb? - person 4ntoine; 02.07.2015
comment
Другой вариант, на который вы можете обратить внимание, это libserialport. Я не знаю, изменилось ли что-нибудь с тех пор, как я написал свой ответ. - person David Grayson; 02.07.2015
comment
к сожалению, мне нужно протестировать lisusb, так как он будет использоваться и будет работать на Linux. - person 4ntoine; 02.07.2015

Если вы хотите использовать какое-либо USB-устройство, которое также распознается последовательным драйвером Apple FTDI, вы можете сначала выгрузить драйвер:

sudo kextunload -b com.apple.driver.AppleUSBFTDI

После этого вы можете использовать его через libusb в обычном режиме.

Для других устройств, которые распознаются как последовательные устройства, вам, вероятно, нужно выгрузить какой-то другой драйвер.

person Pekka Nikander    schedule 14.09.2015

Проблема, по-видимому, связана с конфликтом между разными драйверами, которые используют одни и те же библиотеки, и в моем случае они были связаны с предыдущими установками устройств Samsung. я решил так:

kextstat | grep -v apple

Чтобы получить такой возврат:

  70    0 0x57574000 0x3000     0x2000     com.devguru.driver.SamsungComposite (1.2.4) <33 4 3>
  72    0 0x57831000 0x7000     0x6000     com.devguru.driver.SamsungACMData (1.2.4) <71 33 5 4 3>
  94    0 0x57674000 0x3000     0x2000     com.devguru.driver.SamsungACMControl (1.2.4) <33 4 3>

Потом:

$ sudo kextunload -b com.devguru.driver.SamsungComposite
$ sudo kextunload -b com.devguru.driver.SamsungACMData
$ sudo kextunload -b com.devguru.driver.SamsungACMControl

Сделанный. Наслаждаться

person Luca Scandroglio    schedule 13.05.2020