diff --git a/usr.sbin/bluetooth/ath3kfw/Makefile b/usr.sbin/bluetooth/ath3kfw/Makefile index 26ce06e71a88..61a6f9c35060 100644 --- a/usr.sbin/bluetooth/ath3kfw/Makefile +++ b/usr.sbin/bluetooth/ath3kfw/Makefile @@ -3,5 +3,6 @@ PROG= ath3kfw MAN= ath3kfw.8 LIBADD+= usb +SRCS= main.c ath3k_fw.c ath3k_hw.c .include diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_dbg.h b/usr.sbin/bluetooth/ath3kfw/ath3k_dbg.h new file mode 100644 index 000000000000..3aaf3b3f925e --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_dbg.h @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2013 Adrian Chadd + * 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$ + */ +#ifndef __ATH3K_DEBUG_H__ +#define __ATH3K_DEBUG_H__ + +extern int ath3k_do_debug; +extern int ath3k_do_info; + +#define ath3k_debug(...) if (ath3k_do_debug) fprintf(stderr, __VA_ARGS__) +#define ath3k_err(...) fprintf(stderr, __VA_ARGS__) +#define ath3k_info(...) if (ath3k_do_info) fprintf(stdout, __VA_ARGS__) + +#endif diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_fw.c b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.c new file mode 100644 index 000000000000..2c480044c2d7 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.c @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 2013 Adrian Chadd + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ath3k_fw.h" +#include "ath3k_dbg.h" + +int +ath3k_fw_read(struct ath3k_firmware *fw, const char *fwname) +{ + int fd; + struct stat sb; + unsigned char *buf; + ssize_t r; + int i; + + fd = open(fwname, O_RDONLY); + if (fd < 0) { + warn("%s: open: %s", __func__, fwname); + return (0); + } + + if (fstat(fd, &sb) != 0) { + warn("%s: stat: %s", __func__, fwname); + close(fd); + return (0); + } + + buf = calloc(1, sb.st_size); + if (buf == NULL) { + warn("%s: calloc", __func__); + close(fd); + return (0); + } + + i = 0; + /* XXX handle partial reads */ + r = read(fd, buf, sb.st_size); + if (r < 0) { + warn("%s: read", __func__); + free(buf); + close(fd); + return (0); + } + + if (r != sb.st_size) { + fprintf(stderr, "%s: read len %d != file size %d\n", + __func__, + (int) r, + (int) sb.st_size); + free(buf); + close(fd); + return (0); + } + + /* We have everything, so! */ + + bzero(fw, sizeof(*fw)); + + fw->fwname = strdup(fwname); + fw->len = sb.st_size; + fw->size = sb.st_size; + fw->buf = buf; + + close(fd); + return (1); +} + +void +ath3k_fw_free(struct ath3k_firmware *fw) +{ + if (fw->fwname) + free(fw->fwname); + if (fw->buf) + free(fw->buf); + bzero(fw, sizeof(*fw)); +} diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_fw.h b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.h new file mode 100644 index 000000000000..eddc0b7e4216 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.h @@ -0,0 +1,56 @@ +/*- + * Copyright (c) 2013 Adrian Chadd + * 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$ + */ +#ifndef __ATH3K_FW_H__ +#define __ATH3K_FW_H__ + +/* + * XXX TODO: ensure that the endian-ness of this stuff is + * correct! + */ +struct ath3k_version { + unsigned int rom_version; + unsigned int build_version; + unsigned int ram_version; + unsigned char ref_clock; + unsigned char reserved[0x07]; +}; + +struct ath3k_firmware { + char *fwname; + int len; /* firmware length */ + int size; /* buffer size */ + unsigned char *buf; +}; + +extern int ath3k_fw_read(struct ath3k_firmware *fw, const char *fwname); +extern void ath3k_fw_free(struct ath3k_firmware *fw); + +#endif diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c new file mode 100644 index 000000000000..890e2dbf75a2 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c @@ -0,0 +1,358 @@ +/*- + * Copyright (c) 2013 Adrian Chadd + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ath3k_fw.h" +#include "ath3k_hw.h" +#include "ath3k_dbg.h" + +#define XMIN(x, y) ((x) < (y) ? (x) : (y)) + +int +ath3k_load_fwfile(struct libusb_device_handle *hdl, + const struct ath3k_firmware *fw) +{ + int size, count, sent = 0; + int ret, r; + + count = fw->len; + + size = XMIN(count, FW_HDR_SIZE); + + ath3k_debug("%s: file=%s, size=%d\n", + __func__, fw->fwname, count); + + /* + * Flip the device over to configuration mode. + */ + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, + ATH3K_DNLOAD, + 0, + 0, + fw->buf + sent, + size, + 1000); /* XXX timeout */ + + if (ret != size) { + fprintf(stderr, "Can't switch to config mode; ret=%d\n", + ret); + return (-1); + } + + sent += size; + count -= size; + + /* Load in the rest of the data */ + while (count) { + size = XMIN(count, BULK_SIZE); + ath3k_debug("%s: transferring %d bytes, offset %d\n", + __func__, + sent, + size); + + ret = libusb_bulk_transfer(hdl, + 0x2, + fw->buf + sent, + size, + &r, + 1000); + + if (ret < 0 || r != size) { + fprintf(stderr, "Can't load firmware: err=%s, size=%d\n", + libusb_strerror(ret), + size); + return (-1); + } + sent += size; + count -= size; + } + return (0); +} + +int +ath3k_get_state(struct libusb_device_handle *hdl, unsigned char *state) +{ + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + ATH3K_GETSTATE, + 0, + 0, + state, + 1, + 1000); /* XXX timeout */ + + if (ret < 0) { + fprintf(stderr, + "%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 1); +} + +int +ath3k_get_version(struct libusb_device_handle *hdl, + struct ath3k_version *version) +{ + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + ATH3K_GETVERSION, + 0, + 0, + (unsigned char *) version, + sizeof(struct ath3k_version), + 1000); /* XXX timeout */ + + if (ret < 0) { + fprintf(stderr, + "%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + /* XXX endian fix! */ + + return (ret == sizeof(struct ath3k_version)); +} + +int +ath3k_load_patch(libusb_device_handle *hdl, const char *fw_path) +{ + int ret; + unsigned char fw_state; + struct ath3k_version fw_ver, pt_ver; + char fwname[FILENAME_MAX]; + struct ath3k_firmware fw; + uint32_t tmp; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("%s: Can't get state\n", __func__); + return (ret); + } + + if (fw_state & ATH3K_PATCH_UPDATE) { + ath3k_info("%s: Patch already downloaded\n", + __func__); + return (0); + } + + ret = ath3k_get_version(hdl, &fw_ver); + if (ret < 0) { + ath3k_debug("%s: Can't get version\n", __func__); + return (ret); + } + + /* XXX path info? */ + snprintf(fwname, FILENAME_MAX, "%s/ar3k/AthrBT_0x%08x.dfu", + fw_path, + fw_ver.rom_version); + + /* Read in the firmware */ + if (ath3k_fw_read(&fw, fwname) <= 0) { + ath3k_debug("%s: ath3k_fw_read() failed\n", + __func__); + return (-1); + } + + /* + * Extract the ROM/build version from the patch file. + */ + memcpy(&tmp, fw.buf + fw.len - 8, sizeof(tmp)); + pt_ver.rom_version = le32toh(tmp); + memcpy(&tmp, fw.buf + fw.len - 4, sizeof(tmp)); + pt_ver.build_version = le32toh(tmp); + + ath3k_info("%s: file %s: rom_ver=%d, build_ver=%d\n", + __func__, + fwname, + (int) pt_ver.rom_version, + (int) pt_ver.build_version); + + /* Check the ROM/build version against the firmware */ + if ((pt_ver.rom_version != fw_ver.rom_version) || + (pt_ver.build_version <= fw_ver.build_version)) { + ath3k_debug("Patch file version mismatch!\n"); + ath3k_fw_free(&fw); + return (-1); + } + + /* Load in the firmware */ + ret = ath3k_load_fwfile(hdl, &fw); + + /* free it */ + ath3k_fw_free(&fw); + + return (ret); +} + +int +ath3k_load_syscfg(libusb_device_handle *hdl, const char *fw_path) +{ + unsigned char fw_state; + char filename[FILENAME_MAX]; + struct ath3k_firmware fw; + struct ath3k_version fw_ver; + int clk_value, ret; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("Can't get state to change to load configuration err"); + return (-EBUSY); + } + + ret = ath3k_get_version(hdl, &fw_ver); + if (ret < 0) { + ath3k_err("Can't get version to change to load ram patch err"); + return (ret); + } + + switch (fw_ver.ref_clock) { + case ATH3K_XTAL_FREQ_26M: + clk_value = 26; + break; + case ATH3K_XTAL_FREQ_40M: + clk_value = 40; + break; + case ATH3K_XTAL_FREQ_19P2: + clk_value = 19; + break; + default: + clk_value = 0; + break; +} + + snprintf(filename, FILENAME_MAX, "%s/ar3k/ramps_0x%08x_%d%s", + fw_path, + fw_ver.rom_version, + clk_value, + ".dfu"); + + ath3k_info("%s: syscfg file = %s\n", + __func__, + filename); + + /* Read in the firmware */ + if (ath3k_fw_read(&fw, filename) <= 0) { + ath3k_err("%s: ath3k_fw_read() failed\n", + __func__); + return (-1); + } + + ret = ath3k_load_fwfile(hdl, &fw); + + ath3k_fw_free(&fw); + return (ret); +} + +int +ath3k_set_normal_mode(libusb_device_handle *hdl) +{ + int ret; + unsigned char fw_state; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("%s: can't get state\n", __func__); + return (ret); + } + + /* + * This isn't a fatal error - the device may have detached + * already. + */ + if ((fw_state & ATH3K_MODE_MASK) == ATH3K_NORMAL_MODE) { + ath3k_debug("%s: firmware is already in normal mode\n", + __func__); + return (0); + } + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR, /* XXX out direction? */ + ATH3K_SET_NORMAL_MODE, + 0, + 0, + NULL, + 0, + 1000); /* XXX timeout */ + + if (ret < 0) { + ath3k_err("%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 0); +} + +int +ath3k_switch_pid(libusb_device_handle *hdl) +{ + int ret; + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR, /* XXX set an out flag? */ + USB_REG_SWITCH_VID_PID, + 0, + 0, + NULL, + 0, + 1000); /* XXX timeout */ + + if (ret < 0) { + ath3k_debug("%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 0); +} diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_hw.h b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.h new file mode 100644 index 000000000000..2d72f3f16cfd --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.h @@ -0,0 +1,66 @@ +/*- + * Copyright (c) 2013 Adrian Chadd + * 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$ + */ +#ifndef __ATH3K_HW_H__ +#define __ATH3K_HW_H__ + +#define ATH3K_DNLOAD 0x01 +#define ATH3K_GETSTATE 0x05 +#define ATH3K_SET_NORMAL_MODE 0x07 +#define ATH3K_GETVERSION 0x09 +#define USB_REG_SWITCH_VID_PID 0x0a + +#define ATH3K_MODE_MASK 0x3F +#define ATH3K_NORMAL_MODE 0x0E + +#define ATH3K_PATCH_UPDATE 0x80 +#define ATH3K_SYSCFG_UPDATE 0x40 + +#define ATH3K_XTAL_FREQ_26M 0x00 +#define ATH3K_XTAL_FREQ_40M 0x01 +#define ATH3K_XTAL_FREQ_19P2 0x02 +#define ATH3K_NAME_LEN 0xFF + +#define USB_REQ_DFU_DNLOAD 1 +#define BULK_SIZE 4096 +#define FW_HDR_SIZE 20 + +extern int ath3k_load_fwfile(struct libusb_device_handle *hdl, + const struct ath3k_firmware *fw); +extern int ath3k_get_state(struct libusb_device_handle *hdl, + unsigned char *state); +extern int ath3k_get_version(struct libusb_device_handle *hdl, + struct ath3k_version *version); +extern int ath3k_load_patch(libusb_device_handle *hdl, const char *fw_path); +extern int ath3k_load_syscfg(libusb_device_handle *hdl, const char *fw_path); +extern int ath3k_set_normal_mode(libusb_device_handle *hdl); +extern int ath3k_switch_pid(libusb_device_handle *hdl); + +#endif diff --git a/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 index 3a37343d253a..74855831a702 100644 --- a/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 +++ b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 @@ -1,4 +1,5 @@ .\" Copyright (c) 2010 Maksim Yevmenkin +.\" Copyright (c) 2013, 2016 Adrian Chadd .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -24,16 +25,16 @@ .\" .\" $FreeBSD$ .\" -.Dd November 9, 2010 +.Dd June 4, 2016 .Dt ATH3KFW 8 .Os .Sh NAME .Nm ath3kfw -.Nd firmware download utility for Atheros AR3011 chip based Bluetooth USB devices +.Nd firmware download utility for Atheros AR3011/AR3012 chip based Bluetooth USB devices .Sh SYNOPSIS .Nm .Fl d Ar device_name -.Fl f Ar firmware_file_name +.Fl f Ar firmware_path .Nm .Fl h .Sh DESCRIPTION @@ -45,23 +46,31 @@ device. .Pp This utility will .Em only -work with Atheros AR3011 chip based Bluetooth USB devices. +work with Atheros AR3011 and AR3012 chip based Bluetooth USB devices. The identification is currently based on USB vendor ID/product ID pair. The vendor ID should be 0x0cf3 .Pq Dv USB_VENDOR_ATHEROS2 -and the product ID should be 0x3000. +and the product ID should be one of the supported devices. .Pp -Firmware files ath3k-1.fw and ath3k-2.fw can be obtained from the -linux-firmware RPM. +Firmware files are available in the linux-firmware RPM. +.Pp +The +.Nm +utility will query the device to determine which firmware image and board +configuration to load in at runtime. .Pp The options are as follows: .Bl -tag -width indent +.It Fl D +Enable verbose debugging. .It Fl d Ar device_name Specify .Xr ugen 4 device name. -.It Fl f Ar firmware_file_name -Specify firmware file name for download. +.It I +Enable informational debugging. +.It Fl f Ar firmware_path +Specify the directory containing the firmware files to search and upload. .It Fl h Display usage message and exit. .El @@ -72,7 +81,10 @@ Display usage message and exit. .Xr ugen 4 , .Xr devd 8 .Sh AUTHORS -.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +The original utility was written by +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com . +This was written based on Linux ath3k by +.An Adrian Chadd Aq Mt adrian@freebsd.org . .Sh BUGS Most likely. Please report if found. diff --git a/usr.sbin/bluetooth/ath3kfw/ath3kfw.c b/usr.sbin/bluetooth/ath3kfw/ath3kfw.c deleted file mode 100644 index 37191d1aac4d..000000000000 --- a/usr.sbin/bluetooth/ath3kfw/ath3kfw.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * ath3kfw.c - */ - -/*- - * Copyright (c) 2010 Maksim Yevmenkin - * 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. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, 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 DAMAGE. - * - * $FreeBSD$ - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ATH3KFW "ath3kfw" -#define ATH3KFW_VENDOR_ID 0x0cf3 -#define ATH3KFW_PRODUCT_ID 0x3000 -#define ATH3KFW_FW "/usr/local/etc/ath3k-1.fw" -#define ATH3KFW_BULK_EP 0x02 -#define ATH3KFW_REQ_DFU_DNLOAD 1 -#define ATH3KFW_MAX_BSIZE 4096 - -static int parse_ugen_name (char const *ugen, uint8_t *bus, - uint8_t *addr); -static int find_device (struct libusb20_backend *be, - uint8_t bus, uint8_t addr, - struct libusb20_device **dev); -static int download_firmware (struct libusb20_device *dev, - char const *firmware); -static void usage (void); - -static int vendor_id = ATH3KFW_VENDOR_ID; -static int product_id = ATH3KFW_PRODUCT_ID; - -/* - * Firmware downloader for Atheros AR3011 based USB Bluetooth devices - */ - -int -main(int argc, char **argv) -{ - uint8_t bus, addr; - char const *firmware; - struct libusb20_backend *be; - struct libusb20_device *dev; - int n; - - openlog(ATH3KFW, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_USER); - - bus = 0; - addr = 0; - firmware = ATH3KFW_FW; - - while ((n = getopt(argc, argv, "d:f:hp:v:")) != -1) { - switch (n) { - case 'd': /* ugen device name */ - if (parse_ugen_name(optarg, &bus, &addr) < 0) - usage(); - break; - - case 'f': /* firmware file */ - firmware = optarg; - break; - case 'p': /* product id */ - product_id = strtol(optarg, NULL, 0); - break; - case 'v': /* vendor id */ - vendor_id = strtol(optarg, NULL, 0); - break; - case 'h': - default: - usage(); - break; - /* NOT REACHED */ - } - } - - be = libusb20_be_alloc_default(); - if (be == NULL) { - syslog(LOG_ERR, "libusb20_be_alloc_default() failed"); - return (-1); - } - - if (find_device(be, bus, addr, &dev) < 0) { - syslog(LOG_ERR, "ugen%d.%d is not recognized as " \ - "Atheros AR3011 based device " \ - "(possibly caused by lack of permissions)", bus, addr); - return (-1); - } - - if (download_firmware(dev, firmware) < 0) { - syslog(LOG_ERR, "could not download %s firmare to ugen%d.%d", - firmware, bus, addr); - return (-1); - } - - libusb20_be_free(be); - closelog(); - - 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); -} - -/* - * Find USB device - */ - -static int -find_device(struct libusb20_backend *be, uint8_t bus, uint8_t addr, - struct libusb20_device **dev) -{ - struct LIBUSB20_DEVICE_DESC_DECODED *desc; - - *dev = NULL; - - while ((*dev = libusb20_be_device_foreach(be, *dev)) != NULL) { - if (libusb20_dev_get_bus_number(*dev) != bus || - libusb20_dev_get_address(*dev) != addr) - continue; - - desc = libusb20_dev_get_device_desc(*dev); - if (desc == NULL) - continue; - - if (desc->idVendor != vendor_id || - desc->idProduct != product_id) - continue; - - break; - } - - return ((*dev == NULL)? -1 : 0); -} - -/* - * Download firmware - */ - -static int -download_firmware(struct libusb20_device *dev, char const *firmware) -{ - struct libusb20_transfer *bulk; - struct LIBUSB20_CONTROL_SETUP_DECODED req; - int fd, n, error; - uint8_t buf[ATH3KFW_MAX_BSIZE]; - - error = -1; - - if (libusb20_dev_open(dev, 1) != 0) { - syslog(LOG_ERR, "libusb20_dev_open() failed"); - return (error); - } - - if ((bulk = libusb20_tr_get_pointer(dev, 0)) == NULL) { - syslog(LOG_ERR, "libusb20_tr_get_pointer() failed"); - goto out; - } - - if (libusb20_tr_open(bulk, ATH3KFW_MAX_BSIZE, 1, ATH3KFW_BULK_EP) != 0) { - syslog(LOG_ERR, "libusb20_tr_open(%d, 1, %d) failed", - ATH3KFW_MAX_BSIZE, ATH3KFW_BULK_EP); - goto out; - } - - if ((fd = open(firmware, O_RDONLY)) < 0) { - syslog(LOG_ERR, "open(%s) failed. %s", - firmware, strerror(errno)); - goto out1; - } - - n = read(fd, buf, 20); - if (n != 20) { - syslog(LOG_ERR, "read(%s, 20) failed. %s", - firmware, strerror(errno)); - goto out2; - } - - LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req); - req.bmRequestType = LIBUSB20_REQUEST_TYPE_VENDOR; - req.bRequest = ATH3KFW_REQ_DFU_DNLOAD; - req.wLength = 20; - - if (libusb20_dev_request_sync(dev, &req, buf, NULL, 5000, 0) != 0) { - syslog(LOG_ERR, "libusb20_dev_request_sync() failed"); - goto out2; - } - - for (;;) { - n = read(fd, buf, sizeof(buf)); - if (n < 0) { - syslog(LOG_ERR, "read(%s, %d) failed. %s", - firmware, (int) sizeof(buf), strerror(errno)); - goto out2; - } - if (n == 0) - break; - - libusb20_tr_setup_bulk(bulk, buf, n, 3000); - libusb20_tr_start(bulk); - - while (libusb20_dev_process(dev) == 0) { - if (libusb20_tr_pending(bulk) == 0) - break; - - libusb20_dev_wait_process(dev, -1); - } - - if (libusb20_tr_get_status(bulk) != 0) { - syslog(LOG_ERR, "bulk transfer failed with status %d", - libusb20_tr_get_status(bulk)); - goto out2; - } - } - - error = 0; -out2: - close(fd); -out1: - libusb20_tr_close(bulk); -out: - libusb20_dev_close(dev); - - return (error); -} - -/* - * Display usage and exit - */ - -static void -usage(void) -{ - printf( -"Usage: %s -d ugenX.Y -f firmware_file\n" -"Usage: %s -h\n" \ -"Where:\n" \ -"\t-d ugenX.Y ugen device name\n" \ -"\t-f firmware image firmware image file name for download\n" \ -"\t-v vendor_id vendor id\n" \ -"\t-p vendor_id product id\n" \ -"\t-h display this message\n", ATH3KFW, ATH3KFW); - - exit(255); -} - diff --git a/usr.sbin/bluetooth/ath3kfw/main.c b/usr.sbin/bluetooth/ath3kfw/main.c new file mode 100644 index 000000000000..6ca5206b1507 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/main.c @@ -0,0 +1,394 @@ +/*- + * Copyright (c) 2013 Adrian Chadd + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; +}