#include <sys/param.h>
#include <sys/stat.h>
#include <sys/endian.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libusb.h>
#include "rtlbt_fw.h"
#include "rtlbt_hw.h"
#include "rtlbt_dbg.h"
#define _DEFAULT_RTLBT_FIRMWARE_PATH "/usr/share/firmware/rtlbt"
int rtlbt_do_debug = 0;
int rtlbt_do_info = 0;
struct rtlbt_devid {
uint16_t product_id;
uint16_t vendor_id;
};
static struct rtlbt_devid rtlbt_list[] = {
{ .vendor_id = 0x13d3, .product_id = 0x3529 },
{ .vendor_id = 0x13d3, .product_id = 0x3600 },
{ .vendor_id = 0x04c5, .product_id = 0x165c },
{ .vendor_id = 0x04ca, .product_id = 0x4006 },
{ .vendor_id = 0x0cb8, .product_id = 0xc549 },
{ .vendor_id = 0x04ca, .product_id = 0x4007 },
{ .vendor_id = 0x04c5, .product_id = 0x1675 },
{ .vendor_id = 0x0cb8, .product_id = 0xc558 },
{ .vendor_id = 0x13d3, .product_id = 0x3587 },
{ .vendor_id = 0x13d3, .product_id = 0x3586 },
{ .vendor_id = 0x13d3, .product_id = 0x3592 },
{ .vendor_id = 0x13d3, .product_id = 0x3612 },
{ .vendor_id = 0x0489, .product_id = 0xe122 },
{ .vendor_id = 0x0cb8, .product_id = 0xc559 },
{ .vendor_id = 0x13d3, .product_id = 0x3570 },
{ .vendor_id = 0x13d3, .product_id = 0x3571 },
{ .vendor_id = 0x13d3, .product_id = 0x3572 },
{ .vendor_id = 0x13d3, .product_id = 0x3591 },
{ .vendor_id = 0x0489, .product_id = 0xe123 },
{ .vendor_id = 0x0489, .product_id = 0xe125 },
{ .vendor_id = 0x13d3, .product_id = 0x3617 },
{ .vendor_id = 0x13d3, .product_id = 0x3616 },
{ .vendor_id = 0x0489, .product_id = 0xe130 },
{ .vendor_id = 0x0930, .product_id = 0x021d },
{ .vendor_id = 0x13d3, .product_id = 0x3394 },
{ .vendor_id = 0x0489, .product_id = 0xe085 },
{ .vendor_id = 0x0489, .product_id = 0xe08b },
{ .vendor_id = 0x04f2, .product_id = 0xb49f },
{ .vendor_id = 0x13d3, .product_id = 0x3410 },
{ .vendor_id = 0x13d3, .product_id = 0x3416 },
{ .vendor_id = 0x13d3, .product_id = 0x3459 },
{ .vendor_id = 0x13d3, .product_id = 0x3494 },
{ .vendor_id = 0x7392, .product_id = 0xa611 },
{ .vendor_id = 0x2ff8, .product_id = 0xb011 },
{ .vendor_id = 0x2c4e, .product_id = 0x0115 },
{ .vendor_id = 0x2357, .product_id = 0x0604 },
{ .vendor_id = 0x0b05, .product_id = 0x190e },
{ .vendor_id = 0x2550, .product_id = 0x8761 },
{ .vendor_id = 0x6655, .product_id = 0x8771 },
{ .vendor_id = 0x7392, .product_id = 0xc611 },
{ .vendor_id = 0x2b89, .product_id = 0x8761 },
{ .vendor_id = 0x0b05, .product_id = 0x17dc },
{ .vendor_id = 0x13d3, .product_id = 0x3414 },
{ .vendor_id = 0x13d3, .product_id = 0x3458 },
{ .vendor_id = 0x13d3, .product_id = 0x3461 },
{ .vendor_id = 0x13d3, .product_id = 0x3462 },
{ .vendor_id = 0x13d3, .product_id = 0x3526 },
{ .vendor_id = 0x0b05, .product_id = 0x185c },
{ .vendor_id = 0x04ca, .product_id = 0x4005 },
{ .vendor_id = 0x04c5, .product_id = 0x161f },
{ .vendor_id = 0x0b05, .product_id = 0x18ef },
{ .vendor_id = 0x13d3, .product_id = 0x3548 },
{ .vendor_id = 0x13d3, .product_id = 0x3549 },
{ .vendor_id = 0x13d3, .product_id = 0x3553 },
{ .vendor_id = 0x13d3, .product_id = 0x3555 },
{ .vendor_id = 0x2ff8, .product_id = 0x3051 },
{ .vendor_id = 0x1358, .product_id = 0xc123 },
{ .vendor_id = 0x0cb5, .product_id = 0xc547 },
};
static int
rtlbt_is_realtek(struct libusb_device_descriptor *d)
{
int i;
for (i = 0; i < (int) nitems(rtlbt_list); i++) {
if ((rtlbt_list[i].product_id == d->idProduct) &&
(rtlbt_list[i].vendor_id == d->idVendor)) {
rtlbt_info("found USB Realtek");
return (1);
}
}
return (0);
}
static int
rtlbt_is_bluetooth(struct libusb_device *dev)
{
struct libusb_config_descriptor *cfg;
const struct libusb_interface *ifc;
const struct libusb_interface_descriptor *d;
int r;
r = libusb_get_active_config_descriptor(dev, &cfg);
if (r < 0) {
rtlbt_err("Cannot retrieve config descriptor: %s",
libusb_error_name(r));
return (0);
}
if (cfg->bNumInterfaces != 0) {
ifc = &cfg->interface[0];
if (ifc->num_altsetting != 0) {
d = &ifc->altsetting[0];
if (d->bInterfaceClass == LIBUSB_CLASS_WIRELESS &&
d->bInterfaceSubClass == 0x01 &&
d->bInterfaceProtocol == 0x01) {
rtlbt_info("found USB Realtek");
libusb_free_config_descriptor(cfg);
return (1);
}
}
}
libusb_free_config_descriptor(cfg);
return (0);
}
static libusb_device *
rtlbt_find_device(libusb_context *ctx, int bus_id, int dev_id)
{
libusb_device **list, *dev = NULL, *found = NULL;
struct libusb_device_descriptor d;
ssize_t cnt, i;
int r;
cnt = libusb_get_device_list(ctx, &list);
if (cnt < 0) {
rtlbt_err("libusb_get_device_list() failed: code %lld",
(long long int) cnt);
return (NULL);
}
for (i = 0; i < cnt; i++) {
dev = list[i];
if (bus_id == libusb_get_bus_number(dev) &&
dev_id == libusb_get_device_address(dev)) {
r = libusb_get_device_descriptor(dev, &d);
if (r != 0) {
rtlbt_err("libusb_get_device_descriptor: %s",
libusb_strerror(r));
break;
}
if (rtlbt_is_realtek(&d)) {
found = libusb_ref_device(dev);
break;
}
if (d.idVendor == 0x0bda && rtlbt_is_bluetooth(dev)) {
found = libusb_ref_device(dev);
break;
}
}
}
libusb_free_device_list(list, 1);
return (found);
}
static void
rtlbt_dump_version(ng_hci_read_local_ver_rp *ver)
{
rtlbt_info("hci_version 0x%02x", ver->hci_version);
rtlbt_info("hci_revision 0x%04x", le16toh(ver->hci_revision));
rtlbt_info("lmp_version 0x%02x", ver->lmp_version);
rtlbt_info("lmp_subversion 0x%04x", le16toh(ver->lmp_subversion));
}
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: rtlbtfw (-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[])
{
libusb_context *ctx = NULL;
libusb_device *dev = NULL;
libusb_device_handle *hdl = NULL;
ng_hci_read_local_ver_rp ver;
int r;
uint8_t bus_id = 0, dev_id = 0;
int devid_set = 0;
int n;
char *firmware_dir = NULL;
char *firmware_path = NULL;
char *config_path = NULL;
const char *fw_suffix;
int retcode = 1;
const struct rtlbt_id_table *ic;
uint8_t rom_version;
struct rtlbt_firmware fw, cfg;
enum rtlbt_fw_type fw_type;
uint16_t fw_lmp_subversion;
while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) {
switch (n) {
case 'd':
devid_set = 1;
if (parse_ugen_name(optarg, &bus_id, &dev_id) < 0)
usage();
break;
case 'D':
rtlbt_do_debug = 1;
break;
case 'f':
if (firmware_dir)
free(firmware_dir);
firmware_dir = strdup(optarg);
break;
case 'I':
rtlbt_do_info = 1;
break;
case 'h':
default:
usage();
break;
}
}
if (devid_set == 0) {
usage();
}
r = libusb_init(&ctx);
if (r != 0) {
rtlbt_err("libusb_init failed: code %d", r);
exit(127);
}
rtlbt_debug("opening dev %d.%d", (int) bus_id, (int) dev_id);
dev = rtlbt_find_device(ctx, bus_id, dev_id);
if (dev == NULL) {
rtlbt_err("device not found");
goto shutdown;
}
r = libusb_open(dev, &hdl);
if (r != 0) {
rtlbt_err("libusb_open() failed: code %d", r);
goto shutdown;
}
r = libusb_kernel_driver_active(hdl, 0);
if (r < 0) {
rtlbt_err("libusb_kernel_driver_active() failed: code %d", r);
goto shutdown;
}
if (r > 0) {
rtlbt_info("Firmware has already been downloaded");
retcode = 0;
goto shutdown;
}
r = rtlbt_read_local_ver(hdl, &ver);
if (r < 0) {
rtlbt_err("rtlbt_read_local_ver() failed code %d", r);
goto shutdown;
}
rtlbt_dump_version(&ver);
ic = rtlbt_get_ic(ver.lmp_subversion, ver.hci_revision,
ver.hci_version);
if (ic == NULL) {
rtlbt_err("rtlbt_get_ic() failed: Unknown IC");
goto shutdown;
}
if (firmware_dir == NULL)
firmware_dir = strdup(_DEFAULT_RTLBT_FIRMWARE_PATH);
fw_suffix = ic->fw_suffix == NULL ? "_fw.bin" : ic->fw_suffix;
firmware_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, fw_suffix);
if (firmware_path == NULL)
goto shutdown;
rtlbt_debug("firmware_path = %s", firmware_path);
rtlbt_info("loading firmware %s", firmware_path);
if (rtlbt_fw_read(&fw, firmware_path) <= 0) {
rtlbt_debug("rtlbt_fw_read() failed");
return (-1);
}
fw_type = rtlbt_get_fw_type(&fw, &fw_lmp_subversion);
if (fw_type == RTLBT_FW_TYPE_UNKNOWN &&
(ic->flags & RTLBT_IC_FLAG_SIMPLE) == 0) {
rtlbt_debug("Unknown firmware type");
goto shutdown;
}
if (fw_type != RTLBT_FW_TYPE_UNKNOWN) {
if (fw_lmp_subversion != ver.lmp_subversion) {
rtlbt_err("firmware is for %x but this is a %x",
fw_lmp_subversion, ver.lmp_subversion);
goto shutdown;
}
r = rtlbt_read_rom_ver(hdl, &rom_version);
if (r < 0) {
rtlbt_err("rtlbt_read_rom_ver() failed code %d", r);
goto shutdown;
}
rtlbt_debug("rom_version = %d", rom_version);
if (fw_type == RTLBT_FW_TYPE_V2) {
uint8_t key_id, reg_val[2];
r = rtlbt_read_reg16(hdl, RTLBT_SEC_PROJ, reg_val);
if (r < 0) {
rtlbt_err("rtlbt_read_reg16() failed code %d", r);
goto shutdown;
}
key_id = reg_val[0];
rtlbt_debug("key_id = %d", key_id);
r = rtlbt_parse_fwfile_v2(&fw, rom_version, key_id);
} else
r = rtlbt_parse_fwfile_v1(&fw, rom_version);
if (r < 0) {
rtlbt_err("Parsing firmware file failed");
goto shutdown;
}
config_path = rtlbt_get_fwname(ic->fw_name, firmware_dir,
"_config.bin");
if (config_path == NULL)
goto shutdown;
rtlbt_info("loading config %s", config_path);
if (rtlbt_fw_read(&cfg, config_path) <= 0) {
rtlbt_err("rtlbt_fw_read() failed");
if ((ic->flags & RTLBT_IC_FLAG_CONFIG) != 0)
goto shutdown;
} else {
r = rtlbt_append_fwfile(&fw, &cfg);
rtlbt_fw_free(&cfg);
if (r < 0) {
rtlbt_err("Appending config file failed");
goto shutdown;
}
}
}
r = rtlbt_load_fwfile(hdl, &fw);
if (r < 0) {
rtlbt_debug("Loading firmware file failed");
goto shutdown;
}
rtlbt_fw_free(&fw);
rtlbt_info("Firmware download complete");
r = rtlbt_read_local_ver(hdl, &ver);
if (r < 0) {
rtlbt_err("rtlbt_read_local_ver() failed code %d", r);
goto shutdown;
}
rtlbt_dump_version(&ver);
retcode = 0;
r = libusb_reset_device(hdl);
if (r != 0)
rtlbt_err("libusb_reset_device() failed: %s",
libusb_strerror(r));
shutdown:
if (hdl != NULL)
libusb_close(hdl);
if (dev != NULL)
libusb_unref_device(dev);
if (ctx != NULL)
libusb_exit(ctx);
if (retcode == 0)
rtlbt_info("Firmware download is successful!");
else
rtlbt_err("Firmware download failed!");
return (retcode);
}