summaryrefslogtreecommitdiffstats
path: root/usr.sbin/bluetooth/ath3kfw/main.c
diff options
context:
space:
mode:
authoradrian <adrian@FreeBSD.org>2016-06-07 04:22:18 +0000
committeradrian <adrian@FreeBSD.org>2016-06-07 04:22:18 +0000
commitbe6cf145bec7e3658cd25477592f21b906de159e (patch)
tree74fa8c22d229a55789ea9a5abaf36bba1b8c0b86 /usr.sbin/bluetooth/ath3kfw/main.c
parent93e484e00e1e2dc498a7f4c45c0019e43b8ec5b2 (diff)
downloadFreeBSD-src-be6cf145bec7e3658cd25477592f21b906de159e.zip
FreeBSD-src-be6cf145bec7e3658cd25477592f21b906de159e.tar.gz
[ath3k] add a replacement ath3k firmware loading tool.
This is influenced by the ath3k driver from linux (circa 2013, this is how long I've been sitting on this.) It handles loading in firmware using the newer model, where it assembles the right set of firmware blobs and board configuration based on the device list and querying the device. The older utility could only load in a single image - which sometimes was ath3k-1.fw and sometimes was ath3k-2.fw. However, the ath3k maintainers didn't want to keep adding in binaries that were just derivatives with a separate board config, so they deleted ath3k-2.fw from the Linux firmware repository and instead, well, did this. Now, this has been tested against AR3011 and AR3012 NICs from the AR9285+BT combo up through to the QCA9565+BT combo. It doesn't yet work with the QCAFN222 NIC as that is some newer chip. The firmware can be grabbed from https://github.com/erikarn/ath3kfw/ in the share/firmware/ath3k directory. I'll update this utility over time to support the newer firmware drops (newer than mid-2013) which should pull in the QCNFA222 and subsequent chips. Tested: * AR9285 + BT * AR9287 + BT * AR9485 + BT * AR9462 + BT * QCA9565 + BT
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