| /* |
| * carlu - userspace testing utility for ar9170 devices |
| * |
| * USB back-end driver |
| * |
| * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include "libusb.h" |
| |
| #include "carlu.h" |
| #include "usb.h" |
| #include "debug.h" |
| #include "list.h" |
| #include "cmd.h" |
| |
| #define ADD_DEV(_vid, _pid, _vs, _ps) { \ |
| .idVendor = _vid, \ |
| .idProduct = _pid, \ |
| .vendor_name = _vs, \ |
| .product_name = _ps \ |
| } |
| |
| static const struct { |
| uint16_t idVendor; |
| uint16_t idProduct; |
| char *vendor_name; |
| char *product_name; |
| } dev_list[] = { |
| ADD_DEV(0x0cf3, 0x9170, "Atheros", "9170"), |
| ADD_DEV(0x0cf3, 0x1001, "Atheros", "TG121N"), |
| ADD_DEV(0x0cf3, 0x1002, "TP-Link", "TL-WN821N v2"), |
| ADD_DEV(0xcace, 0x0300, "Cace", "Airpcap NX"), |
| ADD_DEV(0x07d1, 0x3c10, "D-Link", "DWA 160 A1"), |
| ADD_DEV(0x07d1, 0x3a09, "D-Link", "DWA 160 A2"), |
| ADD_DEV(0x0846, 0x9010, "Netgear", "WNDA3100"), |
| ADD_DEV(0x0846, 0x9001, "Netgear", "WN111 v2"), |
| ADD_DEV(0x0ace, 0x1221, "Zydas", "ZD1221"), |
| ADD_DEV(0x0586, 0x3417, "ZyXEL", "NWD271N"), |
| ADD_DEV(0x0cde, 0x0023, "Z-Com", "UB81 BG"), |
| ADD_DEV(0x0cde, 0x0026, "Z-Com", "UB82 ABG"), |
| ADD_DEV(0x0cde, 0x0027, "Sphairon", "Homelink 1202"), |
| ADD_DEV(0x083a, 0xf522, "Arcadyan", "WN7512"), |
| ADD_DEV(0x2019, 0x5304, "Planex", "GWUS300"), |
| ADD_DEV(0x04bb, 0x093f, "IO-Data", "WNGDNUS2"), |
| ADD_DEV(0x057C, 0x8401, "AVM", "FRITZ!WLAN USB Stick N"), |
| ADD_DEV(0x057C, 0x8402, "AVM", "FRITZ!WLAN USB Stick N 2.4"), |
| }; |
| |
| static libusb_context *usb_ctx; |
| static LIST_HEAD(active_dev_list); |
| |
| static int carlusb_event_thread(void *_ar) |
| { |
| struct carlu *ar = (void *)_ar; |
| dbg("event thread active and polling.\n"); |
| |
| while (!ar->stop_event_polling) |
| libusb_handle_events(ar->ctx); |
| |
| dbg("==> event thread desixed.\n"); |
| return 0; |
| } |
| |
| static int carlusb_is_ar9170(struct libusb_device_descriptor *desc) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(dev_list); i++) { |
| if ((desc->idVendor == dev_list[i].idVendor) && |
| (desc->idProduct == dev_list[i].idProduct)) { |
| dbg("== found device \"%s %s\" [0x%04x:0x%04x]\n", |
| dev_list[i].vendor_name, dev_list[i].product_name, |
| desc->idVendor, desc->idProduct); |
| |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| static bool carlusb_is_dev(struct carlu *iter, |
| struct libusb_device *dev) |
| { |
| libusb_device *list_dev; |
| |
| if (!iter->dev) |
| return false; |
| |
| list_dev = libusb_get_device(iter->dev); |
| |
| if (libusb_get_bus_number(list_dev) == libusb_get_bus_number(dev) && |
| libusb_get_device_address(list_dev) == libusb_get_device_address(dev)) |
| return true; |
| |
| return false; |
| } |
| |
| int carlusb_show_devinfo(struct carlu *ar) |
| { |
| struct libusb_device_descriptor desc; |
| libusb_device *dev; |
| int err; |
| |
| dev = libusb_get_device(ar->dev); |
| |
| err = libusb_get_device_descriptor(dev, &desc); |
| if (err) |
| return err; |
| |
| info("USB Device Information:\n"); |
| info("\tUSB VendorID:%.4x(%s), ProductID:%.4x(%s)\n", |
| dev_list[ar->idx].idVendor, dev_list[ar->idx].vendor_name, |
| dev_list[ar->idx].idProduct, dev_list[ar->idx].product_name); |
| info("\tBus:%d Address:%d\n", libusb_get_bus_number(dev), |
| libusb_get_device_address(dev)); |
| |
| return 0; |
| } |
| |
| static int carlusb_get_dev(struct carlu *ar, bool reset) |
| { |
| struct carlu *iter; |
| libusb_device_handle *dev; |
| libusb_device **list; |
| int ret, err, i, idx = -1; |
| |
| ret = libusb_get_device_list(usb_ctx, &list); |
| if (ret < 0) { |
| err("usb device enum failed (%d)\n", ret); |
| return ret; |
| } |
| |
| for (i = 0; i < ret; i++) { |
| struct libusb_device_descriptor desc; |
| |
| memset(&desc, 0, sizeof(desc)); |
| err = libusb_get_device_descriptor(list[i], &desc); |
| if (err != 0) |
| continue; |
| |
| idx = carlusb_is_ar9170(&desc); |
| if (idx < 0) |
| continue; |
| |
| list_for_each_entry(iter, &active_dev_list, dev_list) { |
| if (carlusb_is_dev(iter, list[i])) { |
| err = -EALREADY; |
| break; |
| } |
| } |
| |
| if (err) |
| continue; |
| |
| err = libusb_open(list[i], &dev); |
| if (err != 0) { |
| err("failed to open device (%d)\n", err); |
| continue; |
| } |
| |
| err = libusb_kernel_driver_active(dev, 0); |
| switch (err) { |
| case 0: |
| break; |
| default: |
| err("failed to aquire exculusive access (%d).\n", err); |
| goto skip; |
| } |
| |
| if (reset) { |
| err = libusb_reset_device(dev); |
| if (err != 0) { |
| err("failed to reset device (%d)\n", err); |
| goto skip; |
| } |
| } |
| |
| err = libusb_claim_interface(dev, 0); |
| if (err == 0) { |
| dbg(">device is now under our control.\n"); |
| break; |
| } else { |
| err("failed to claim device (%d)\n", err); |
| goto skip; |
| } |
| |
| skip: |
| libusb_close(dev); |
| } |
| |
| if (i != ret) { |
| ar->idx = idx; |
| ar->ctx = usb_ctx; |
| ar->dev = dev; |
| list_add_tail(&ar->dev_list, &active_dev_list); |
| ret = 0; |
| } else { |
| ret = -ENODEV; |
| } |
| |
| libusb_free_device_list(list, 1); |
| return ret; |
| } |
| |
| static void carlusb_tx_cb(struct carlu *ar, |
| struct frame *frame) |
| { |
| if (ar->tx_cb) |
| ar->tx_cb(ar, frame); |
| |
| ar->tx_octets += frame->len; |
| |
| carlu_free_frame(ar, frame); |
| } |
| |
| static void carlusb_zap_queues(struct carlu *ar) |
| { |
| struct frame *frame; |
| |
| BUG_ON(SDL_mutexP(ar->tx_queue_lock) != 0); |
| while (!list_empty(&ar->tx_queue)) { |
| frame = list_first_entry(&ar->tx_queue, struct frame, dcb.list); |
| list_del(&frame->dcb.list); |
| carlusb_tx_cb(ar, frame); |
| } |
| SDL_mutexV(ar->tx_queue_lock); |
| } |
| |
| static void carlusb_free_driver(struct carlu *ar) |
| { |
| if (!IS_ERR_OR_NULL(ar)) { |
| if (ar->event_pipe[0] > -1) |
| close(ar->event_pipe[0]); |
| |
| if (ar->event_pipe[1] > -1) |
| close(ar->event_pipe[1]); |
| |
| carlusb_zap_queues(ar); |
| carlfw_release(ar->fw); |
| ar->fw = NULL; |
| |
| if (ar->dev) { |
| libusb_release_interface(ar->dev, 0); |
| libusb_close(ar->dev); |
| ar->dev = NULL; |
| } |
| carlu_free_driver(ar); |
| } |
| } |
| |
| static int carlusb_init(struct carlu *ar) |
| { |
| init_list_head(&ar->tx_queue); |
| ar->tx_queue_lock = SDL_CreateMutex(); |
| ar->event_pipe[0] = ar->event_pipe[1] = -1; |
| |
| return 0; |
| } |
| |
| static struct carlu *carlusb_open(void) |
| { |
| struct carlu *tmp; |
| int err; |
| |
| tmp = carlu_alloc_driver(sizeof(*tmp)); |
| if (tmp == NULL) |
| return NULL; |
| |
| err = carlusb_init(tmp); |
| if (err < 0) |
| goto err_out; |
| |
| err = carlusb_get_dev(tmp, true); |
| if (err < 0) |
| goto err_out; |
| |
| return tmp; |
| |
| err_out: |
| carlusb_free_driver(tmp); |
| return ERR_PTR(err); |
| } |
| |
| static void carlusb_cancel_rings(struct carlu *ar) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(ar->rx_ring); i++) |
| libusb_cancel_transfer(ar->rx_ring[i]); |
| |
| libusb_cancel_transfer(ar->rx_interrupt); |
| } |
| |
| static void carlusb_free_rings(struct carlu *ar) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(ar->rx_ring); i++) |
| libusb_free_transfer(ar->rx_ring[i]); |
| |
| libusb_free_transfer(ar->rx_interrupt); |
| } |
| |
| static void carlusb_destroy(struct carlu *ar) |
| { |
| int event_thread_status; |
| |
| dbg("==>release device.\n"); |
| |
| ar->stop_event_polling = true; |
| |
| carlusb_cancel_rings(ar); |
| |
| SDL_WaitThread(ar->event_thread, &event_thread_status); |
| |
| carlusb_free_rings(ar); |
| list_del(&ar->dev_list); |
| } |
| |
| static void carlusb_tx_bulk_cb(struct libusb_transfer *transfer); |
| |
| static void carlusb_tx_pending(struct carlu *ar) |
| { |
| struct frame *frame; |
| struct libusb_transfer *urb; |
| int err; |
| |
| BUG_ON(SDL_mutexP(ar->tx_queue_lock) != 0); |
| if (ar->tx_queue_len >= (AR9170_TX_MAX_ACTIVE_URBS) || |
| list_empty(&ar->tx_queue)) |
| goto out; |
| |
| ar->tx_queue_len++; |
| |
| urb = libusb_alloc_transfer(0); |
| if (urb == NULL) |
| goto out; |
| |
| frame = list_first_entry(&ar->tx_queue, struct frame, dcb.list); |
| list_del(&frame->dcb.list); |
| |
| if (ar->tx_stream) { |
| struct ar9170_stream *tx_stream; |
| |
| tx_stream = frame_push(frame, sizeof(*tx_stream)); |
| tx_stream->length = cpu_to_le16(frame->len); |
| tx_stream->tag = cpu_to_le16(0x697e); |
| } |
| |
| libusb_fill_bulk_transfer(urb, ar->dev, AR9170_EP_TX, (unsigned char *) |
| frame->data, frame->len, carlusb_tx_bulk_cb, (void *)frame, 0); |
| |
| /* FIXME: ZERO_PACKET support! */ |
| urb->flags |= LIBUSB_TRANSFER_FREE_TRANSFER; |
| /* urb->flags |= LIBUSB_TRANSFER_ZERO_PACKET; */ |
| frame->dev = (void *) ar; |
| frame_get(frame); |
| |
| err = libusb_submit_transfer(urb); |
| if (err != 0) { |
| err("->usb bulk tx submit failed (%d).\n", err); |
| libusb_free_transfer(urb); |
| } |
| |
| out: |
| SDL_mutexV(ar->tx_queue_lock); |
| return; |
| } |
| |
| void carlusb_tx(struct carlu *ar, struct frame *frame) |
| { |
| BUG_ON(SDL_mutexP(ar->tx_queue_lock) != 0); |
| |
| list_add_tail(&frame->dcb.list, &ar->tx_queue); |
| SDL_mutexV(ar->tx_queue_lock); |
| |
| carlusb_tx_pending(ar); |
| } |
| |
| static void carlusb_tx_bulk_cb(struct libusb_transfer *transfer) |
| { |
| struct frame *frame = (void *) transfer->user_data; |
| struct carlu *ar = (void *) frame->dev; |
| |
| BUG_ON(SDL_mutexP(ar->tx_queue_lock) != 0); |
| ar->tx_queue_len--; |
| SDL_mutexV(ar->tx_queue_lock); |
| |
| if (ar->tx_stream) |
| frame_pull(frame, 4); |
| |
| carlusb_tx_cb(ar, frame); |
| carlusb_tx_pending(ar); |
| } |
| |
| static void carlusb_rx_interrupt_cb(struct libusb_transfer *transfer) |
| { |
| struct carlu *ar = (void *) transfer->user_data; |
| int err; |
| |
| switch (transfer->status) { |
| case LIBUSB_TRANSFER_COMPLETED: |
| carlu_handle_command(ar, transfer->buffer, transfer->actual_length); |
| break; |
| |
| case LIBUSB_TRANSFER_CANCELLED: |
| return; |
| |
| default: |
| err("==>rx_irq urb died (%d)\n", transfer->status); |
| break; |
| } |
| |
| err = libusb_submit_transfer(transfer); |
| if (err != 0) |
| err("==>rx_irq urb resubmit failed (%d)\n", err); |
| } |
| |
| static void carlusb_rx_bulk_cb(struct libusb_transfer *transfer) |
| { |
| struct frame *frame = (void *) transfer->user_data; |
| struct carlu *ar = (void *) frame->dev; |
| int err; |
| |
| switch (transfer->status) { |
| case LIBUSB_TRANSFER_COMPLETED: |
| frame_put(frame, transfer->actual_length); |
| |
| carlu_rx(ar, frame); |
| |
| frame_trim(frame, 0); |
| break; |
| |
| case LIBUSB_TRANSFER_CANCELLED: |
| return; |
| |
| default: |
| err("==>rx_bulk urb died (%d)\n", transfer->status); |
| break; |
| } |
| |
| err = libusb_submit_transfer(transfer); |
| if (err != 0) |
| err("->rx_bulk urb resubmit failed (%d)\n", err); |
| } |
| |
| static int carlusb_initialize_rxirq(struct carlu *ar) |
| { |
| int err; |
| |
| ar->rx_interrupt = libusb_alloc_transfer(0); |
| if (ar->rx_interrupt == NULL) { |
| err("==>cannot alloc rx interrupt urb\n"); |
| return -1; |
| } |
| |
| libusb_fill_interrupt_transfer(ar->rx_interrupt, ar->dev, AR9170_EP_IRQ, |
| (unsigned char *)&ar->irq_buf, sizeof(ar->irq_buf), |
| carlusb_rx_interrupt_cb, (void *) ar, 0); |
| |
| err = libusb_submit_transfer(ar->rx_interrupt); |
| if (err != 0) { |
| err("==>failed to submit rx interrupt (%d)\n", err); |
| return err; |
| } |
| |
| dbg("rx interrupt is now operational.\n"); |
| return 0; |
| } |
| |
| static int carlusb_initialize_rxrings(struct carlu *ar) |
| { |
| struct frame *tmp; |
| unsigned int i; |
| int err; |
| |
| for (i = 0; i < ARRAY_SIZE(ar->rx_ring); i++) { |
| tmp = frame_alloc(ar->rx_max); |
| if (tmp == NULL) |
| return -ENOMEM; |
| |
| tmp->dev = (void *) ar; |
| |
| ar->rx_ring[i] = libusb_alloc_transfer(0); |
| if (ar->rx_ring[i] == NULL) { |
| frame_free(tmp); |
| return -ENOMEM; |
| } |
| |
| libusb_fill_bulk_transfer(ar->rx_ring[i], ar->dev, |
| AR9170_EP_RX, (unsigned char *)tmp->data, |
| ar->rx_max, carlusb_rx_bulk_cb, (void *)tmp, 0); |
| |
| err = libusb_submit_transfer(ar->rx_ring[i]); |
| if (err != 0) { |
| err("==>failed to submit rx buld urb (%d)\n", err); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| dbg("rx ring is now ready to receive.\n"); |
| return 0; |
| } |
| |
| static int carlusb_load_firmware(struct carlu *ar) |
| { |
| int ret = 0; |
| |
| dbg("loading firmware...\n"); |
| |
| ar->fw = carlfw_load(CARL9170_FIRMWARE_FILE); |
| if (IS_ERR_OR_NULL(ar->fw)) |
| return PTR_ERR(ar->fw); |
| |
| ret = carlu_fw_check(ar); |
| if (ret) |
| return ret; |
| |
| ret = carlusb_fw_check(ar); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int carlusb_upload_firmware(struct carlu *ar, bool boot) |
| { |
| uint32_t addr = 0x200000; |
| size_t len; |
| void *buf; |
| int ret = 0; |
| |
| dbg("initiating firmware image upload procedure.\n"); |
| |
| buf = carlfw_get_fw(ar->fw, &len); |
| if (IS_ERR_OR_NULL(buf)) |
| return PTR_ERR(buf); |
| |
| if (ar->miniboot_size) { |
| dbg("Miniboot firmware size:%d\n", ar->miniboot_size); |
| len -= ar->miniboot_size; |
| buf += ar->miniboot_size; |
| } |
| |
| while (len) { |
| int blocklen = len > 4096 ? 4096 : len; |
| |
| ret = libusb_control_transfer(ar->dev, 0x40, 0x30, addr >> 8, 0, buf, blocklen, 1000); |
| if (ret != blocklen && ret != LIBUSB_ERROR_TIMEOUT) { |
| err("==>firmware upload failed (%d)\n", ret); |
| return -EXIT_FAILURE; |
| } |
| |
| dbg("uploaded %d bytes to start address 0x%04x.\n", blocklen, addr); |
| |
| buf += blocklen; |
| addr += blocklen; |
| len -= blocklen; |
| } |
| |
| if (boot) { |
| ret = libusb_control_transfer(ar->dev, 0x40, 0x31, 0, 0, NULL, 0, 5000); |
| if (ret < 0) { |
| err("unable to boot firmware (%d)\n", ret); |
| return -EXIT_FAILURE; |
| } |
| |
| /* give the firmware some time to reset & reboot */ |
| SDL_Delay(100); |
| |
| /* |
| * since the device did a full usb reset, |
| * we have to get a new "dev". |
| */ |
| libusb_release_interface(ar->dev, 0); |
| libusb_close(ar->dev); |
| ar->dev = NULL; |
| list_del(&ar->dev_list); |
| |
| ret = carlusb_get_dev(ar, false); |
| } |
| |
| return 0; |
| } |
| |
| int carlusb_cmd_async(struct carlu *ar, struct carl9170_cmd *cmd, |
| const bool free_buf) |
| { |
| struct libusb_transfer *urb; |
| int ret, send; |
| |
| if (cmd->hdr.len > (CARL9170_MAX_CMD_LEN - 4)) { |
| err("|-> too much payload\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (cmd->hdr.len % 4) { |
| err("|-> invalid command length\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = libusb_interrupt_transfer(ar->dev, AR9170_EP_CMD, (void *) cmd, cmd->hdr.len + 4, &send, 32); |
| if (ret != 0) { |
| err("OID:0x%.2x failed due to (%d) (%d).\n", cmd->hdr.cmd, ret, send); |
| print_hex_dump_bytes(ERROR, "CMD:", cmd, cmd->hdr.len); |
| } |
| |
| out: |
| if (free_buf) |
| free((void *)cmd); |
| |
| return ret; |
| } |
| |
| int carlusb_cmd(struct carlu *ar, uint8_t oid, |
| uint8_t *cmd, size_t clen, |
| uint8_t *rsp, size_t rlen) |
| { |
| int ret, send; |
| |
| if (clen > (CARL9170_MAX_CMD_LEN - 4)) { |
| err("|-> OID:0x%.2x has too much payload (%d octs)\n", oid, (int)clen); |
| return -EINVAL; |
| } |
| |
| ret = SDL_mutexP(ar->resp_lock); |
| if (ret != 0) { |
| err("failed to acquire resp_lock.\n"); |
| print_hex_dump_bytes(ERROR, "CMD:", ar->cmd.buf, clen); |
| return -1; |
| } |
| |
| ar->cmd.cmd.hdr.len = clen; |
| ar->cmd.cmd.hdr.cmd = oid; |
| /* buf[2] & buf[3] are padding */ |
| if (clen && cmd != (uint8_t *)(&ar->cmd.cmd.data)) |
| memcpy(&ar->cmd.cmd.data, cmd, clen); |
| |
| ar->resp_buf = (uint8_t *)rsp; |
| ar->resp_len = rlen; |
| |
| ret = carlusb_cmd_async(ar, &ar->cmd.cmd, false); |
| if (ret != 0) { |
| err("OID:0x%.2x failed due to (%d) (%d).\n", oid, ret, send); |
| print_hex_dump_bytes(ERROR, "CMD:", ar->cmd.buf, clen); |
| SDL_mutexV(ar->resp_lock); |
| return ret; |
| } |
| |
| ret = SDL_CondWaitTimeout(ar->resp_pend, ar->resp_lock, 100); |
| if (ret != 0) { |
| err("|-> OID:0x%.2x timed out %d.\n", oid, ret); |
| ar->resp_buf = NULL; |
| ar->resp_len = 0; |
| ret = -ETIMEDOUT; |
| } |
| |
| SDL_mutexV(ar->resp_lock); |
| return ret; |
| } |
| |
| struct carlu *carlusb_probe(void) |
| { |
| struct carlu *ar; |
| int ret = -ENOMEM; |
| |
| ar = carlusb_open(); |
| if (IS_ERR_OR_NULL(ar)) { |
| if (IS_ERR(ar)) |
| ret = PTR_ERR(ar); |
| goto err_out; |
| } |
| |
| ret = carlusb_show_devinfo(ar); |
| if (ret) |
| goto err_out; |
| |
| ret = carlusb_load_firmware(ar); |
| if (ret) |
| goto err_out; |
| |
| ret = pipe(ar->event_pipe); |
| if (ret) |
| goto err_out; |
| |
| ar->stop_event_polling = false; |
| ar->event_thread = SDL_CreateThread(carlusb_event_thread, ar); |
| |
| ret = carlusb_upload_firmware(ar, true); |
| if (ret) |
| goto err_kill; |
| |
| ret = carlusb_initialize_rxirq(ar); |
| if (ret) |
| goto err_kill; |
| |
| ret = carlusb_initialize_rxrings(ar); |
| if (ret) |
| goto err_kill; |
| |
| ret = carlu_cmd_echo(ar, 0x44110dee); |
| if (ret) { |
| err("echo test failed...\n"); |
| goto err_kill; |
| } |
| |
| info("firmware is active and running.\n"); |
| |
| carlu_fw_info(ar); |
| |
| return ar; |
| |
| err_kill: |
| carlusb_destroy(ar); |
| |
| err_out: |
| carlusb_free_driver(ar); |
| err("usb device rendezvous failed (%d).\n", ret); |
| return ERR_PTR(ret); |
| } |
| |
| void carlusb_close(struct carlu *ar) |
| { |
| carlu_cmd_reboot(ar); |
| |
| carlusb_destroy(ar); |
| carlusb_free_driver(ar); |
| } |
| |
| int carlusb_print_known_devices(void) |
| { |
| unsigned int i; |
| |
| debug_level = INFO; |
| |
| info("==> dumping known device list <==\n"); |
| for (i = 0; i < ARRAY_SIZE(dev_list); i++) { |
| info("Vendor:\"%-9s\" Product:\"%-26s\" => USBID:[0x%04x:0x%04x]\n", |
| dev_list[i].vendor_name, dev_list[i].product_name, |
| dev_list[i].idVendor, dev_list[i].idProduct); |
| } |
| info("==> end of device list <==\n"); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| int usb_init(void) |
| { |
| int ret; |
| |
| ret = libusb_init(&usb_ctx); |
| if (ret != 0) { |
| err("failed to initialize usb subsystem (%d)\n", ret); |
| return ret; |
| } |
| |
| /* like a silent chatterbox! */ |
| libusb_set_debug(usb_ctx, 2); |
| |
| return 0; |
| } |
| |
| void usb_exit(void) |
| { |
| libusb_exit(usb_ctx); |
| } |