[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
This commit is contained in:
adrian 2016-06-07 04:22:18 +00:00
parent 93e484e00e
commit be6cf145be
9 changed files with 1052 additions and 307 deletions

View File

@ -3,5 +3,6 @@
PROG= ath3kfw
MAN= ath3kfw.8
LIBADD+= usb
SRCS= main.c ath3k_fw.c ath3k_hw.c
.include <bsd.prog.mk>

View File

@ -0,0 +1,41 @@
/*-
* 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$
*/
#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

View File

@ -0,0 +1,114 @@
/*-
* 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 <sys/types.h>
#include <sys/stat.h>
#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));
}

View File

@ -0,0 +1,56 @@
/*-
* 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$
*/
#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

View File

@ -0,0 +1,358 @@
/*-
* 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 <sys/endian.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <libusb.h>
#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);
}

View File

@ -0,0 +1,66 @@
/*-
* 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$
*/
#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

View File

@ -1,4 +1,5 @@
.\" Copyright (c) 2010 Maksim Yevmenkin <m_evmenkin@yahoo.com>
.\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@freebsd.org>
.\" 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.

View File

@ -1,297 +0,0 @@
/*
* ath3kfw.c
*/
/*-
* Copyright (c) 2010 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* 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 <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <libusb20_desc.h>
#include <libusb20.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#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);
}

View File

@ -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;
}