diff options
Diffstat (limited to 'carl9170fw/tools/carlu/src/usb.c')
-rw-r--r-- | carl9170fw/tools/carlu/src/usb.c | 793 |
1 files changed, 793 insertions, 0 deletions
diff --git a/carl9170fw/tools/carlu/src/usb.c b/carl9170fw/tools/carlu/src/usb.c new file mode 100644 index 0000000..ebc19a9 --- /dev/null +++ b/carl9170fw/tools/carlu/src/usb.c @@ -0,0 +1,793 @@ +/* + * 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); +} |