opnsense-src/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c
Vladimir Kondratyev ccfbbe2d8a rtlbtfw: Firmware loader for Realtek 87XX/88XX bluetooth USB adaptors
Firmware files are available in the comms/rtlbt-firmware port.

Sponsored by:	Future Crew LLC
MFC after:	1 month
Differential Revision:	https://reviews.freebsd.org/D46739

(cherry picked from commit 5036d9652a5701d00e9e40ea942c278e9f77d33d)
2024-12-22 06:34:35 +03:00

236 lines
6.1 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
* Copyright (c) 2023 Future Crew LLC.
*
* 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.
*/
#include <sys/param.h>
#include <sys/endian.h>
#include <sys/stat.h>
#include <netgraph/bluetooth/include/ng_hci.h>
#include <err.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <libusb.h>
#include "rtlbt_fw.h"
#include "rtlbt_hw.h"
#include "rtlbt_dbg.h"
static int
rtlbt_hci_command(struct libusb_device_handle *hdl, struct rtlbt_hci_cmd *cmd,
void *event, int size, int *transferred, int timeout)
{
struct timespec to, now, remains;
int ret;
ret = libusb_control_transfer(hdl,
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE,
0,
0,
0,
(uint8_t *)cmd,
RTLBT_HCI_CMD_SIZE(cmd),
timeout);
if (ret < 0) {
rtlbt_err("libusb_control_transfer() failed: err=%s",
libusb_strerror(ret));
return (ret);
}
clock_gettime(CLOCK_MONOTONIC, &now);
to = RTLBT_MSEC2TS(timeout);
timespecadd(&to, &now, &to);
do {
timespecsub(&to, &now, &remains);
ret = libusb_interrupt_transfer(hdl,
RTLBT_INTERRUPT_ENDPOINT_ADDR,
event,
size,
transferred,
RTLBT_TS2MSEC(remains) + 1);
if (ret < 0) {
rtlbt_err("libusb_interrupt_transfer() failed: err=%s",
libusb_strerror(ret));
return (ret);
}
switch (((struct rtlbt_hci_event *)event)->header.event) {
case NG_HCI_EVENT_COMMAND_COMPL:
if (*transferred <
(int)offsetof(struct rtlbt_hci_event_cmd_compl, data))
break;
if (cmd->opcode !=
((struct rtlbt_hci_event_cmd_compl *)event)->opcode)
break;
return (0);
default:
break;
}
rtlbt_debug("Stray HCI event: %x",
((struct rtlbt_hci_event *)event)->header.event);
} while (timespeccmp(&to, &now, >));
rtlbt_err("libusb_interrupt_transfer() failed: err=%s",
libusb_strerror(LIBUSB_ERROR_TIMEOUT));
return (LIBUSB_ERROR_TIMEOUT);
}
int
rtlbt_read_local_ver(struct libusb_device_handle *hdl,
ng_hci_read_local_ver_rp *ver)
{
int ret, transferred;
struct rtlbt_hci_event_cmd_compl *event;
struct rtlbt_hci_cmd cmd = {
.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_INFO,
NG_HCI_OCF_READ_LOCAL_VER)),
.length = 0,
};
uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(ng_hci_read_local_ver_rp)];
memset(buf, 0, sizeof(buf));
ret = rtlbt_hci_command(hdl,
&cmd,
buf,
sizeof(buf),
&transferred,
RTLBT_HCI_CMD_TIMEOUT);
if (ret < 0 || transferred != sizeof(buf)) {
rtlbt_debug("Can't read local version: code=%d, size=%d",
ret,
transferred);
return (-1);
}
event = (struct rtlbt_hci_event_cmd_compl *)buf;
memcpy(ver, event->data, sizeof(ng_hci_read_local_ver_rp));
return (0);
}
int
rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver)
{
int ret, transferred;
struct rtlbt_hci_event_cmd_compl *event;
struct rtlbt_hci_cmd cmd = {
.opcode = htole16(0xfc6d),
.length = 0,
};
uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_rom_ver_rp)];
memset(buf, 0, sizeof(buf));
ret = rtlbt_hci_command(hdl,
&cmd,
buf,
sizeof(buf),
&transferred,
RTLBT_HCI_CMD_TIMEOUT);
if (ret < 0 || transferred != sizeof(buf)) {
rtlbt_debug("Can't read ROM version: code=%d, size=%d",
ret,
transferred);
return (-1);
}
event = (struct rtlbt_hci_event_cmd_compl *)buf;
*ver = ((struct rtlbt_rom_ver_rp *)event->data)->version;
return (0);
}
int
rtlbt_load_fwfile(struct libusb_device_handle *hdl,
const struct rtlbt_firmware *fw)
{
uint8_t cmd_buf[RTLBT_HCI_MAX_CMD_SIZE];
struct rtlbt_hci_cmd *cmd = (struct rtlbt_hci_cmd *)cmd_buf;
struct rtlbt_hci_dl_cmd *dl_cmd = (struct rtlbt_hci_dl_cmd *)cmd->data;
uint8_t evt_buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_hci_dl_rp)];
uint8_t *data = fw->buf;
int frag_num = fw->len / RTLBT_MAX_CMD_DATA_LEN + 1;
int frag_len = RTLBT_MAX_CMD_DATA_LEN;
int i;
int ret, transferred;
for (i = 0; i < frag_num; i++) {
rtlbt_debug("download fw (%d/%d)", i + 1, frag_num);
memset(cmd_buf, 0, sizeof(cmd_buf));
cmd->opcode = htole16(0xfc20);
if (i > 0x7f)
dl_cmd->index = (i & 0x7f) + 1;
else
dl_cmd->index = i;
if (i == (frag_num - 1)) {
dl_cmd->index |= 0x80; /* data end */
frag_len = fw->len % RTLBT_MAX_CMD_DATA_LEN;
}
cmd->length = frag_len + 1;
memcpy(dl_cmd->data, data, frag_len);
/* Send download command */
ret = rtlbt_hci_command(hdl,
cmd,
evt_buf,
sizeof(evt_buf),
&transferred,
RTLBT_HCI_CMD_TIMEOUT);
if (ret < 0) {
rtlbt_err("download fw command failed (%d)", errno);
goto out;
}
if (transferred != sizeof(evt_buf)) {
rtlbt_err("download fw event length mismatch");
errno = EIO;
ret = -1;
goto out;
}
data += RTLBT_MAX_CMD_DATA_LEN;
}
out:
return (ret);
}