summaryrefslogtreecommitdiffstats
path: root/usr.sbin/bluetooth/ath3kfw/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/bluetooth/ath3kfw/main.c')
-rw-r--r--usr.sbin/bluetooth/ath3kfw/main.c394
1 files changed, 394 insertions, 0 deletions
diff --git a/usr.sbin/bluetooth/ath3kfw/main.c b/usr.sbin/bluetooth/ath3kfw/main.c
new file mode 100644
index 0000000..6ca5206
--- /dev/null
+++ b/usr.sbin/bluetooth/ath3kfw/main.c
@@ -0,0 +1,394 @@
+/*-
+ * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
+ * redistribution must be conditioned upon including a substantially
+ * similar Disclaimer requirement for further binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <libusb.h>
+
+#include "ath3k_fw.h"
+#include "ath3k_hw.h"
+#include "ath3k_dbg.h"
+
+#define _DEFAULT_ATH3K_FIRMWARE_PATH "/usr/share/firmware/ath3k/"
+
+int ath3k_do_debug = 0;
+int ath3k_do_info = 0;
+
+struct ath3k_devid {
+ uint16_t product_id;
+ uint16_t vendor_id;
+ int is_3012;
+};
+
+static struct ath3k_devid ath3k_list[] = {
+
+ /* Atheros AR3012 with sflash firmware*/
+ { .vendor_id = 0x0489, .product_id = 0xe04e, .is_3012 = 1 },
+ { .vendor_id = 0x0489, .product_id = 0xe04d, .is_3012 = 1 },
+ { .vendor_id = 0x0489, .product_id = 0xe056, .is_3012 = 1 },
+ { .vendor_id = 0x0489, .product_id = 0xe057, .is_3012 = 1 },
+ { .vendor_id = 0x0489, .product_id = 0xe05f, .is_3012 = 1 },
+ { .vendor_id = 0x04c5, .product_id = 0x1330, .is_3012 = 1 },
+ { .vendor_id = 0x04ca, .product_id = 0x3004, .is_3012 = 1 },
+ { .vendor_id = 0x04ca, .product_id = 0x3005, .is_3012 = 1 },
+ { .vendor_id = 0x04ca, .product_id = 0x3006, .is_3012 = 1 },
+ { .vendor_id = 0x04ca, .product_id = 0x3008, .is_3012 = 1 },
+ { .vendor_id = 0x04ca, .product_id = 0x300b, .is_3012 = 1 },
+ { .vendor_id = 0x0930, .product_id = 0x0219, .is_3012 = 1 },
+ { .vendor_id = 0x0930, .product_id = 0x0220, .is_3012 = 1 },
+ { .vendor_id = 0x0b05, .product_id = 0x17d0, .is_3012 = 1 },
+ { .vendor_id = 0x0CF3, .product_id = 0x0036, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0x3004, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0x3005, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0x3008, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0x311D, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0x311E, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0x311F, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0x3121, .is_3012 = 1 },
+ { .vendor_id = 0x0CF3, .product_id = 0x817a, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0xe004, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0xe005, .is_3012 = 1 },
+ { .vendor_id = 0x0cf3, .product_id = 0xe003, .is_3012 = 1 },
+ { .vendor_id = 0x13d3, .product_id = 0x3362, .is_3012 = 1 },
+ { .vendor_id = 0x13d3, .product_id = 0x3375, .is_3012 = 1 },
+ { .vendor_id = 0x13d3, .product_id = 0x3393, .is_3012 = 1 },
+ { .vendor_id = 0x13d3, .product_id = 0x3402, .is_3012 = 1 },
+
+ /* Atheros AR5BBU22 with sflash firmware */
+ { .vendor_id = 0x0489, .product_id = 0xE036, .is_3012 = 1 },
+ { .vendor_id = 0x0489, .product_id = 0xE03C, .is_3012 = 1 },
+};
+
+static int
+ath3k_is_3012(struct libusb_device_descriptor *d)
+{
+ int i;
+
+ /* Search looking for whether it's an AR3012 */
+ for (i = 0; i < (int) nitems(ath3k_list); i++) {
+ if ((ath3k_list[i].product_id == d->idProduct) &&
+ (ath3k_list[i].vendor_id == d->idVendor)) {
+ fprintf(stderr, "%s: found AR3012\n", __func__);
+ return (ath3k_list[i].is_3012);
+ }
+ }
+
+ /* Not found */
+ return (0);
+}
+
+static libusb_device *
+ath3k_find_device(libusb_context *ctx, int bus_id, int dev_id)
+{
+ libusb_device **list, *dev = NULL, *found = NULL;
+ ssize_t cnt, i;
+
+ cnt = libusb_get_device_list(ctx, &list);
+ if (cnt < 0) {
+ ath3k_err("%s: libusb_get_device_list() failed: code %lld\n",
+ __func__,
+ (long long int) cnt);
+ return (NULL);
+ }
+
+ /*
+ * XXX TODO: match on the vendor/product id too!
+ */
+ for (i = 0; i < cnt; i++) {
+ dev = list[i];
+ if (bus_id == libusb_get_bus_number(dev) &&
+ dev_id == libusb_get_device_address(dev)) {
+ /*
+ * Take a reference so it's not freed later on.
+ */
+ found = libusb_ref_device(dev);
+ break;
+ }
+ }
+
+ libusb_free_device_list(list, 1);
+ return (found);
+}
+
+static int
+ath3k_init_ar3012(libusb_device_handle *hdl, const char *fw_path)
+{
+ int ret;
+
+ ret = ath3k_load_patch(hdl, fw_path);
+ if (ret < 0) {
+ ath3k_err("Loading patch file failed\n");
+ return (ret);
+ }
+
+ ret = ath3k_load_syscfg(hdl, fw_path);
+ if (ret < 0) {
+ ath3k_err("Loading sysconfig file failed\n");
+ return (ret);
+ }
+
+ ret = ath3k_set_normal_mode(hdl);
+ if (ret < 0) {
+ ath3k_err("Set normal mode failed\n");
+ return (ret);
+ }
+
+ ath3k_switch_pid(hdl);
+ return (0);
+}
+
+static int
+ath3k_init_firmware(libusb_device_handle *hdl, const char *file_prefix)
+{
+ struct ath3k_firmware fw;
+ char fwname[FILENAME_MAX];
+ int ret;
+
+ /* XXX path info? */
+ snprintf(fwname, FILENAME_MAX, "%s/ath3k-1.fw", file_prefix);
+
+ ath3k_debug("%s: loading ath3k-1.fw\n", __func__);
+
+ /* Read in the firmware */
+ if (ath3k_fw_read(&fw, fwname) <= 0) {
+ fprintf(stderr, "%s: ath3k_fw_read() failed\n",
+ __func__);
+ return (-1);
+ }
+
+ /* Load in the firmware */
+ ret = ath3k_load_fwfile(hdl, &fw);
+
+ /* free it */
+ ath3k_fw_free(&fw);
+
+ return (0);
+}
+
+/*
+ * Parse ugen name and extract device's bus and address
+ */
+
+static int
+parse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr)
+{
+ char *ep;
+
+ if (strncmp(ugen, "ugen", 4) != 0)
+ return (-1);
+
+ *bus = (uint8_t) strtoul(ugen + 4, &ep, 10);
+ if (*ep != '.')
+ return (-1);
+
+ *addr = (uint8_t) strtoul(ep + 1, &ep, 10);
+ if (*ep != '\0')
+ return (-1);
+
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: ath3kfw (-D) -d ugenX.Y (-f firmware path) (-I)\n");
+ fprintf(stderr, " -D: enable debugging\n");
+ fprintf(stderr, " -d: device to operate upon\n");
+ fprintf(stderr, " -f: firmware path, if not default\n");
+ fprintf(stderr, " -I: enable informational output\n");
+ exit(127);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct libusb_device_descriptor d;
+ libusb_context *ctx;
+ libusb_device *dev;
+ libusb_device_handle *hdl;
+ unsigned char state;
+ struct ath3k_version ver;
+ int r;
+ uint8_t bus_id = 0, dev_id = 0;
+ int devid_set = 0;
+ int n;
+ char *firmware_path = NULL;
+ int is_3012 = 0;
+
+ /* libusb setup */
+ r = libusb_init(&ctx);
+ if (r != 0) {
+ ath3k_err("%s: libusb_init failed: code %d\n",
+ argv[0],
+ r);
+ exit(127);
+ }
+
+ /* Enable debugging, just because */
+ libusb_set_debug(ctx, 3);
+
+ /* Parse command line arguments */
+ while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) {
+ switch (n) {
+ case 'd': /* ugen device name */
+ devid_set = 1;
+ if (parse_ugen_name(optarg, &bus_id, &dev_id) < 0)
+ usage();
+ break;
+ case 'D':
+ ath3k_do_debug = 1;
+ break;
+ case 'f': /* firmware path */
+ if (firmware_path)
+ free(firmware_path);
+ firmware_path = strdup(optarg);
+ break;
+ case 'I':
+ ath3k_do_info = 1;
+ break;
+ case 'h':
+ default:
+ usage();
+ break;
+ /* NOT REACHED */
+ }
+ }
+
+ /* Ensure the devid was given! */
+ if (devid_set == 0) {
+ usage();
+ /* NOTREACHED */
+ }
+
+ ath3k_debug("%s: opening dev %d.%d\n",
+ basename(argv[0]),
+ (int) bus_id,
+ (int) dev_id);
+
+ /* Find a device based on the bus/dev id */
+ dev = ath3k_find_device(ctx, bus_id, dev_id);
+ if (dev == NULL) {
+ ath3k_err("%s: device not found\n", __func__);
+ /* XXX cleanup? */
+ exit(1);
+ }
+
+ /* Get the device descriptor for this device entry */
+ r = libusb_get_device_descriptor(dev, &d);
+ if (r != 0) {
+ warn("%s: libusb_get_device_descriptor: %s\n",
+ __func__,
+ libusb_strerror(r));
+ exit(1);
+ }
+
+ /* See if its an AR3012 */
+ if (ath3k_is_3012(&d)) {
+ is_3012 = 1;
+
+ /* If it's bcdDevice > 1, don't attach */
+ if (d.bcdDevice > 0x0001) {
+ ath3k_debug("%s: AR3012; bcdDevice=%d, exiting\n",
+ __func__,
+ d.bcdDevice);
+ exit(0);
+ }
+ }
+
+ /* XXX enforce that bInterfaceNumber is 0 */
+
+ /* XXX enforce the device/product id if they're non-zero */
+
+ /* Grab device handle */
+ r = libusb_open(dev, &hdl);
+ if (r != 0) {
+ ath3k_err("%s: libusb_open() failed: code %d\n", __func__, r);
+ /* XXX cleanup? */
+ exit(1);
+ }
+
+ /*
+ * Get the initial NIC state.
+ */
+ r = ath3k_get_state(hdl, &state);
+ if (r == 0) {
+ ath3k_err("%s: ath3k_get_state() failed!\n", __func__);
+ /* XXX cleanup? */
+ exit(1);
+ }
+ ath3k_debug("%s: state=0x%02x\n",
+ __func__,
+ (int) state);
+
+ /* And the version */
+ r = ath3k_get_version(hdl, &ver);
+ if (r == 0) {
+ ath3k_err("%s: ath3k_get_version() failed!\n", __func__);
+ /* XXX cleanup? */
+ exit(1);
+ }
+ ath3k_info("ROM version: %d, build version: %d, ram version: %d, "
+ "ref clock=%d\n",
+ ver.rom_version,
+ ver.build_version,
+ ver.ram_version,
+ ver.ref_clock);
+
+ /* Default the firmware path */
+ if (firmware_path == NULL)
+ firmware_path = strdup(_DEFAULT_ATH3K_FIRMWARE_PATH);
+
+ if (is_3012) {
+ (void) ath3k_init_ar3012(hdl, firmware_path);
+ } else {
+ (void) ath3k_init_firmware(hdl, firmware_path);
+ }
+
+ /* Shutdown */
+ libusb_close(hdl);
+ hdl = NULL;
+
+ libusb_unref_device(dev);
+ dev = NULL;
+
+ libusb_exit(ctx);
+ ctx = NULL;
+}
OpenPOWER on IntegriCloud