/*
|
* Copyright (C) 2013 Realtek Semiconductor Corp.
|
*
|
* This program is free software; you can redistribute it and/or modify
|
* it under the terms of the GNU General Public License as published by
|
* the Free Software Foundation; either version 2 of the License, or
|
* (at your option) any later version.
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*
|
* Module Name:
|
* hciattach_rtk.c
|
*
|
* Description:
|
* H4/H5 specific initialization
|
*
|
* Revision History:
|
* Date Version Author Comment
|
* ---------- --------- --------------- -----------------------
|
* 2013-06-06 1.0.0 gordon_yang Create
|
* 2013-06-18 1.0.1 lory_xu add support for multi fw
|
* 2013-06-21 1.0.2 gordon_yang add timeout for get version cmd
|
* 2013-07-01 1.0.3 lory_xu close file handle
|
* 2013-07-01 2.0 champion_chen add IC check
|
* 2013-12-16 2.1 champion_chen fix bug in Additional packet number
|
* 2013-12-25 2.2 champion_chen open host flow control after send last fw packet
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
#include <config.h>
|
#endif
|
|
#include <stdio.h>
|
#include <errno.h>
|
#include <unistd.h>
|
#include <stdlib.h>
|
#include <termios.h>
|
#include <time.h>
|
#include <sys/time.h>
|
#include <sys/types.h>
|
#include <sys/param.h>
|
#include <sys/ioctl.h>
|
#include <sys/socket.h>
|
#include <sys/uio.h>
|
#include <sys/stat.h>
|
#include <fcntl.h>
|
#include <signal.h>
|
#include <stdint.h>
|
#include <string.h>
|
#include <endian.h>
|
#include <byteswap.h>
|
#include <netinet/in.h>
|
|
#include "hciattach.h"
|
|
#define RTK_VERSION "4.1"
|
|
/* #define RTL_8703A_SUPPORT */
|
|
#define USE_CUSTOMER_ADDRESS
|
|
#define BAUDRATE_4BYTES
|
#define FIRMWARE_DIRECTORY "/lib/firmware/rtlbt/"
|
#define BT_CONFIG_DIRECTORY "/lib/firmware/rtlbt/"
|
|
#ifdef USE_CUSTOMER_ADDRESS
|
#define BT_ADDR_FILE "/opt/bdaddr"
|
static uint8_t customer_bdaddr = 0;
|
#endif
|
|
#define CONFIG_TXPOWER (1 << 0)
|
#define CONFIG_XTAL (1 << 1)
|
#define CONFIG_BTMAC (1 << 2)
|
|
#define EXTRA_CONFIG_OPTION
|
#ifdef EXTRA_CONFIG_OPTION
|
#define EXTRA_CONFIG_FILE "/opt/rtk_btconfig.txt"
|
static uint32_t config_flags;
|
static uint8_t txpower_cfg[4];
|
static uint8_t txpower_len;
|
static uint8_t xtal_cfg;
|
#endif
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
#define cpu_to_le16(d) (d)
|
#define cpu_to_le32(d) (d)
|
#define le16_to_cpu(d) (d)
|
#define le32_to_cpu(d) (d)
|
#elif __BYTE_ORDER == __BIG_ENDIAN
|
#define cpu_to_le16(d) bswap_16(d)
|
#define cpu_to_le32(d) bswap_32(d)
|
#define le16_to_cpu(d) bswap_16(d)
|
#define le32_to_cpu(d) bswap_32(d)
|
#else
|
#error "Unknown byte order"
|
#endif
|
|
typedef uint8_t RT_U8, *PRT_U8;
|
typedef int8_t RT_S8, *PRT_S8;
|
typedef uint16_t RT_U16, *PRT_U16;
|
typedef int32_t RT_S32, *PRT_S32;
|
typedef uint32_t RT_U32, *PRT_U32;
|
|
RT_U8 DBG_ON = 1;
|
#define LOG_STR "Realtek Bluetooth"
|
#define RS_DBG(fmt, arg...) \
|
do{ \
|
if (DBG_ON) \
|
fprintf(stderr, "%s :" fmt "\n" , LOG_STR, ##arg); \
|
}while(0)
|
|
#define RS_INFO(fmt, arg...) \
|
do{ \
|
fprintf(stderr, "%s :" fmt "\n", LOG_STR, ##arg); \
|
}while(0)
|
|
#define RS_ERR(fmt, arg...) \
|
do{ \
|
fprintf(stderr, "%s ERROR: " fmt "\n", LOG_STR, ##arg); \
|
}while(0)
|
|
#define HCI_COMMAND_HDR_SIZE 3
|
#define HCI_EVENT_HDR_SIZE 2
|
/* #define RTK_PATCH_LENGTH_MAX 24576 */ //24*1024
|
#define RTK_PATCH_LENGTH_MAX (40 * 1024)
|
#define PATCH_DATA_FIELD_MAX_SIZE 252
|
#define READ_DATA_SIZE 16
|
#define H5_MAX_RETRY_COUNT 40
|
|
#define RTK_VENDOR_CONFIG_MAGIC 0x8723ab55
|
const RT_U8 RTK_EPATCH_SIGNATURE[8] =
|
{ 0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68 };
|
const RT_U8 Extension_Section_SIGNATURE[4] = { 0x51, 0x04, 0xFD, 0x77 };
|
|
#define HCI_CMD_READ_BD_ADDR 0x1009
|
#define HCI_VENDOR_CHANGE_BDRATE 0xfc17
|
#define HCI_VENDOR_READ_RTK_ROM_VERISION 0xfc6d
|
#define HCI_CMD_READ_LOCAL_VERISION 0x1001
|
#define HCI_VENDOR_READ_CHIP_TYPE 0xfc61
|
|
#define ROM_LMP_NONE 0x0000
|
#define ROM_LMP_8723a 0x1200
|
#define ROM_LMP_8723b 0x8723
|
#define ROM_LMP_8821a 0x8821
|
#define ROM_LMP_8761a 0x8761
|
#define ROM_LMP_8761btc 0x8763
|
|
#define ROM_LMP_8703a 0x87b3
|
#define ROM_LMP_8763a 0x8763
|
#define ROM_LMP_8703b 0x8703
|
#define ROM_LMP_8723c 0x87c3 /* ??????? */
|
#define ROM_LMP_8822b 0x8822
|
#define ROM_LMP_8723cs_xx 0x8704
|
#define ROM_LMP_8723cs_cg 0x8705
|
#define ROM_LMP_8723cs_vf 0x8706
|
|
/* Chip type */
|
#define CHIP_8703AS 1
|
#define CHIP_8723CS_CG 3
|
#define CHIP_8723CS_VF 4
|
#define CHIP_RTL8723CS_XX 5
|
#define CHIP_8703BS 7
|
|
/* software id */
|
#define CHIP_UNKNOWN 0x00
|
#define CHIP_8761AT 0x1F
|
#define CHIP_8761ATF 0x2F
|
#define CHIP_8761BTC 0x3F
|
#define CHIP_8761B 0x4F
|
#define CHIP_BEFORE 0x6F
|
#define CHIP_8822BS 0x70
|
#define CHIP_8723DS 0x71
|
#define CHIP_8821CS 0x72
|
|
/* HCI data types */
|
#define H5_ACK_PKT 0x00
|
#define HCI_COMMAND_PKT 0x01
|
#define HCI_ACLDATA_PKT 0x02
|
#define HCI_SCODATA_PKT 0x03
|
#define HCI_EVENT_PKT 0x04
|
#define H5_VDRSPEC_PKT 0x0E
|
#define H5_LINK_CTL_PKT 0x0F
|
|
#define H5_HDR_SEQ(hdr) ((hdr)[0] & 0x07)
|
#define H5_HDR_ACK(hdr) (((hdr)[0] >> 3) & 0x07)
|
#define H5_HDR_CRC(hdr) (((hdr)[0] >> 6) & 0x01)
|
#define H5_HDR_RELIABLE(hdr) (((hdr)[0] >> 7) & 0x01)
|
#define H5_HDR_PKT_TYPE(hdr) ((hdr)[1] & 0x0f)
|
#define H5_HDR_LEN(hdr) ((((hdr)[1] >> 4) & 0xff) + ((hdr)[2] << 4))
|
#define H5_HDR_SIZE 4
|
|
struct sk_buff {
|
RT_U32 max_len;
|
RT_U32 data_len;
|
RT_U8 data[0];
|
};
|
|
/* Skb helpers */
|
struct bt_skb_cb {
|
RT_U8 pkt_type;
|
RT_U8 incoming;
|
RT_U16 expect;
|
RT_U8 tx_seq;
|
RT_U8 retries;
|
RT_U8 sar;
|
unsigned short channel;
|
};
|
|
typedef struct {
|
uint8_t index;
|
uint8_t data[252];
|
} __attribute__ ((packed)) download_vendor_patch_cp;
|
|
struct hci_command_hdr {
|
RT_U16 opcode;
|
RT_U8 plen;
|
} __attribute__ ((packed));
|
|
struct hci_event_hdr {
|
RT_U8 evt;
|
RT_U8 plen;
|
} __attribute__ ((packed));
|
|
struct hci_ev_cmd_complete {
|
RT_U8 ncmd;
|
RT_U16 opcode;
|
} __attribute__ ((packed));
|
|
struct rtk_bt_vendor_config_entry {
|
RT_U16 offset;
|
RT_U8 entry_len;
|
RT_U8 entry_data[0];
|
} __attribute__ ((packed));
|
|
struct rtk_bt_vendor_config {
|
RT_U32 signature;
|
RT_U16 data_len;
|
struct rtk_bt_vendor_config_entry entry[0];
|
} __attribute__ ((packed));
|
|
struct rtk_epatch_entry {
|
RT_U16 chipID;
|
RT_U16 patch_length;
|
RT_U32 start_offset;
|
RT_U32 svn_ver;
|
RT_U32 coex_ver;
|
} __attribute__ ((packed));
|
|
struct rtk_epatch {
|
RT_U8 signature[8];
|
RT_U32 fw_version;
|
RT_U16 number_of_patch;
|
struct rtk_epatch_entry entry[0];
|
} __attribute__ ((packed));
|
|
struct rtk_extension_entry {
|
uint8_t opcode;
|
uint8_t length;
|
uint8_t *data;
|
} __attribute__ ((packed));
|
|
typedef enum _RTK_ROM_VERSION_CMD_STATE {
|
cmd_not_send,
|
cmd_has_sent,
|
event_received
|
} RTK_ROM_VERSION_CMD_STATE;
|
|
typedef enum _H5_RX_STATE {
|
H5_W4_PKT_DELIMITER,
|
H5_W4_PKT_START,
|
H5_W4_HDR,
|
H5_W4_DATA,
|
H5_W4_CRC
|
} H5_RX_STATE;
|
|
typedef enum _H5_RX_ESC_STATE {
|
H5_ESCSTATE_NOESC,
|
H5_ESCSTATE_ESC
|
} H5_RX_ESC_STATE;
|
|
typedef enum _H5_LINK_STATE {
|
H5_SYNC,
|
H5_CONFIG,
|
H5_INIT,
|
H5_PATCH,
|
H5_ACTIVE
|
} H5_LINK_STATE;
|
|
uint16_t project_id[]=
|
{
|
ROM_LMP_8723a,
|
ROM_LMP_8723b, /* RTL8723BS */
|
ROM_LMP_8821a, /* RTL8821AS */
|
ROM_LMP_8761a, /* RTL8761ATV */
|
|
ROM_LMP_8703a,
|
ROM_LMP_8763a,
|
ROM_LMP_8703b,
|
ROM_LMP_8723c, /* index 7 for 8723CS. What is for other 8723CS */
|
ROM_LMP_8822b, /* RTL8822BS */
|
ROM_LMP_8723b, /* RTL8723DS */
|
ROM_LMP_8821a, /* id 10 for RTL8821CS, lmp subver 0x8821 */
|
ROM_LMP_NONE
|
};
|
|
#define RTL_FW_MATCH_CHIP_TYPE (1 << 0)
|
#define RTL_FW_MATCH_HCI_VER (1 << 1)
|
#define RTL_FW_MATCH_HCI_REV (1 << 2)
|
struct patch_info {
|
uint32_t match_flags;
|
uint8_t chip_type;
|
uint16_t lmp_subver;
|
uint16_t proj_id;
|
uint8_t hci_ver;
|
uint16_t hci_rev;
|
char *patch_file;
|
char *config_file;
|
char *ic_name;
|
};
|
|
static struct patch_info h4_patch_table[] = {
|
/* match flags, chip type, lmp subver, proj id(unused), hci_ver,
|
* hci_rev, ...
|
*/
|
|
/* RTL8761AT */
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8761AT,
|
0x8761, 0xffff, 0, 0x000a,
|
"rtl8761at_fw", "rtl8761at_config", "RTL8761AT" },
|
/* RTL8761ATF */
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8761ATF,
|
0x8761, 0xffff, 0, 0x000a,
|
"rtl8761atf_fw", "rtl8761atf_config", "RTL8761ATF" },
|
/* RTL8761B TC
|
* FW/Config is not used.
|
*/
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8761BTC,
|
0x8763, 0xffff, 0, 0x000b,
|
"rtl8761btc_fw", "rtl8761btc_config", "RTL8761BTC" },
|
/* RTL8761B */
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8761B,
|
0x8761, 0xffff, 0, 0x000b,
|
"rtl8761b_fw", "rtl8761b_config", "RTL8761B" },
|
|
{ 0, 0, 0, ROM_LMP_NONE, 0, 0, "rtl_none_fw", "rtl_none_config", "NONE"}
|
};
|
|
static struct patch_info patch_table[] = {
|
/* match flags, chip type, lmp subver, proj id(unused), hci_ver,
|
* hci_rev, ...
|
*/
|
|
/* RTL8723AS */
|
{ 0, 0, ROM_LMP_8723a, ROM_LMP_8723a, 0, 0,
|
"rtl8723a_fw", "rtl8723a_config", "RTL8723AS"},
|
/* RTL8821CS */
|
{ RTL_FW_MATCH_HCI_REV, CHIP_8821CS,
|
ROM_LMP_8821a, ROM_LMP_8821a, 0, 0x000c,
|
"rtl8821c_fw", "rtl8821c_config", "RTL8821CS"},
|
/* RTL8821AS */
|
{ 0, 0, ROM_LMP_8821a, ROM_LMP_8821a, 0, 0,
|
"rtl8821a_fw", "rtl8821a_config", "RTL8821AS"},
|
/* RTL8761ATV */
|
{ 0, 0, ROM_LMP_8761a, ROM_LMP_8761a, 0, 0,
|
"rtl8761a_fw", "rtl8761a_config", "RTL8761ATV"},
|
|
/* RTL8703AS
|
* RTL8822BS
|
* */
|
#ifdef RTL_8703A_SUPPORT
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8703AS,
|
ROM_LMP_8723b, ROM_LMP_8723b, 0, 0,
|
"rtl8703a_fw", "rtl8703a_config", "RTL8703AS"},
|
#endif
|
{ 0, CHIP_8822BS, ROM_LMP_8822b, ROM_LMP_8822b, 0, 0,
|
"rtl8822b_fw", "rtl8822b_config", "RTL8822BS"},
|
|
/* RTL8703BS
|
* RTL8723CS_XX
|
* RTL8723CS_CG
|
* RTL8723CS_VF
|
* */
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8703BS,
|
ROM_LMP_8703b, ROM_LMP_8703b, 0, 0,
|
"rtl8703b_fw", "rtl8703b_config", "RTL8703BS"},
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_RTL8723CS_XX,
|
ROM_LMP_8703b, ROM_LMP_8723cs_xx, 0, 0,
|
"rtl8723cs_xx_fw", "rtl8723cs_xx_config", "RTL8723CS_XX"},
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8723CS_CG,
|
ROM_LMP_8703b, ROM_LMP_8723cs_cg, 0, 0,
|
"rtl8723cs_cg_fw", "rtl8723cs_cg_config", "RTL8723CS_CG"},
|
{ RTL_FW_MATCH_CHIP_TYPE, CHIP_8723CS_VF,
|
ROM_LMP_8703b, ROM_LMP_8723cs_vf, 0, 0,
|
"rtl8723cs_vf_fw", "rtl8723cs_vf_config", "RTL8723CS_VF"},
|
|
/* RTL8723BS */
|
{ RTL_FW_MATCH_HCI_VER | RTL_FW_MATCH_HCI_REV, 0,
|
ROM_LMP_8723b, ROM_LMP_8723b, 6, 0x000b,
|
"rtl8723b_fw", "rtl8723b_config", "RTL8723BS"},
|
/* RTL8723DS */
|
{ RTL_FW_MATCH_HCI_VER | RTL_FW_MATCH_HCI_REV, CHIP_8723DS,
|
ROM_LMP_8723b, ROM_LMP_8723b, 8, 0x000d,
|
"rtl8723d_fw", "rtl8723d_config", "RTL8723DS"},
|
/* add entries here*/
|
|
{ 0, 0, 0, ROM_LMP_NONE, 0, 0, "rtl_none_fw", "rtl_none_config", "NONE"}
|
};
|
|
typedef struct btrtl_info {
|
/**********************h5 releated*************************/
|
RT_U8 rxseq_txack; /* expected rx seq number */
|
RT_U8 rxack; /* last packet sent by us that the peer ack'ed */
|
RT_U8 use_crc;
|
RT_U8 is_txack_req; /* txack required */
|
RT_U8 msgq_txseq; /* next pkt seq */
|
RT_U16 message_crc;
|
RT_U32 rx_count; /* expected pkts to recv */
|
|
H5_RX_STATE rx_state;
|
H5_RX_ESC_STATE rx_esc_state;
|
H5_LINK_STATE link_estab_state;
|
|
struct sk_buff *rx_skb;
|
struct sk_buff *host_last_cmd;
|
|
uint16_t num_of_cmd_sent;
|
RT_U16 lmp_subver;
|
uint16_t hci_rev;
|
uint8_t hci_ver;
|
RT_U8 eversion;
|
int h5_max_retries;
|
RT_U8 chip_type;
|
|
/**********************patch related************************/
|
uint32_t baudrate;
|
uint8_t dl_fw_flag;
|
int serial_fd;
|
int hw_flow_control;
|
int final_speed;
|
int total_num; /* total pkt number */
|
int tx_index; /* current sending pkt number */
|
int rx_index; /* ack index from board */
|
int fw_len; /* fw patch file len */
|
int config_len; /* config patch file len */
|
int total_len; /* fw & config extracted buf len */
|
uint8_t *fw_buf; /* fw patch file buf */
|
uint8_t *config_buf; /* config patch file buf */
|
uint8_t *total_buf; /* fw & config extracted buf */
|
RTK_ROM_VERSION_CMD_STATE rom_version_cmd_state;
|
RTK_ROM_VERSION_CMD_STATE hci_version_cmd_state;
|
RTK_ROM_VERSION_CMD_STATE chip_type_cmd_state;
|
|
struct patch_info *patch_ent;
|
|
int proto;
|
} rtk_hw_cfg_t;
|
|
static rtk_hw_cfg_t rtk_hw_cfg;
|
|
void util_hexdump(const uint8_t *buf, size_t len)
|
{
|
static const char hexdigits[] = "0123456789abcdef";
|
char str[16 * 3];
|
size_t i;
|
|
if (!buf || !len)
|
return;
|
|
for (i = 0; i < len; i++) {
|
str[((i % 16) * 3)] = hexdigits[buf[i] >> 4];
|
str[((i % 16) * 3) + 1] = hexdigits[buf[i] & 0xf];
|
str[((i % 16) * 3) + 2] = ' ';
|
if ((i + 1) % 16 == 0) {
|
str[16 * 3 - 1] = '\0';
|
RS_INFO("%s", str);
|
}
|
}
|
|
if (i % 16 > 0) {
|
str[(i % 16) * 3 - 1] = '\0';
|
RS_INFO("%s", str);
|
}
|
}
|
|
#ifdef EXTRA_CONFIG_OPTION
|
|
static inline int xtalset_supported(void)
|
{
|
struct patch_info *pent = rtk_hw_cfg.patch_ent;
|
|
/* Only support rtl8723ds, rtl8822bs and rtl8821cs xtal config */
|
if (pent->chip_type != CHIP_8723DS &&
|
pent->chip_type != CHIP_8822BS &&
|
pent->chip_type != CHIP_8821CS)
|
return 0;
|
|
return 1;
|
}
|
|
static void line_proc(char *buff, int len)
|
{
|
char *argv[32];
|
int nargs = 0;
|
RS_INFO("%s", buff);
|
|
while (nargs < 32) {
|
/* skip any white space */
|
while ((*buff == ' ') || (*buff == '\t') ||
|
*buff == ',') {
|
++buff;
|
}
|
|
if (*buff == '\0') {
|
/* end of line, no more args */
|
argv[nargs] = NULL;
|
break;
|
}
|
|
/* begin of argument string */
|
argv[nargs++] = buff;
|
|
/* find end of string */
|
while (*buff && (*buff != ' ') && (*buff != '\t')) {
|
++buff;
|
}
|
|
if (*buff == '\r' || *buff == '\n')
|
++buff;
|
|
if (*buff == '\0') {
|
/* end of line, no more args */
|
argv[nargs] = NULL;
|
break;
|
}
|
|
*buff++ = '\0'; /* terminate current arg */
|
}
|
|
/* valid config */
|
if (nargs >= 4) {
|
unsigned long int offset;
|
uint8_t l;
|
uint8_t i = 0;
|
|
offset = strtoul(argv[0], NULL, 16);
|
offset = offset | (strtoul(argv[1], NULL, 16) << 8);
|
RS_INFO("extra config offset %04lx", offset);
|
l = strtoul(argv[2], NULL, 16);
|
if (l != (uint8_t)(nargs - 3)) {
|
RS_ERR("invalid len %u", l);
|
return;
|
}
|
|
if (offset == 0x015b && l <= 4) {
|
/* Tx power */
|
for (i = 0; i < l; i++)
|
txpower_cfg[i] = (uint8_t)strtoul(argv[3 + i], NULL, 16);
|
txpower_len = l;
|
config_flags |= CONFIG_TXPOWER;
|
} else if (offset == 0x01e6) {
|
/* XTAL for 8822B, 8821C 8723D */
|
xtal_cfg = (uint8_t)strtoul(argv[3], NULL, 16);
|
config_flags |= CONFIG_XTAL;
|
} else {
|
RS_ERR("extra config %04lx is not supported", offset);
|
}
|
}
|
}
|
|
static void config_proc(uint8_t *buff, int len)
|
{
|
uint8_t *head = buff;
|
uint8_t *ptr = buff;
|
int l;
|
|
while (len > 0) {
|
ptr = strchr(head, '\n');
|
if (!ptr)
|
break;
|
*ptr++ = '\0';
|
while (*head == ' ' || *head == '\t')
|
head++;
|
l = ptr - head;
|
if (l > 0 && *head != '#')
|
line_proc(head, l);
|
head = ptr;
|
len -= l;
|
}
|
}
|
|
static void config_file_proc(const char *path)
|
{
|
int fd;
|
uint8_t buff[256];
|
int result;
|
|
fd = open(path, O_RDONLY);
|
if (fd == -1) {
|
RS_INFO("Couldnt open extra config %s, %s", path, strerror(errno));
|
return;
|
}
|
|
result = read(fd, buff, sizeof(buff));
|
if (result == -1) {
|
RS_ERR("Couldnt read %s, %s", path, strerror(errno));
|
close(fd);
|
return;
|
} else if (result == 0) {
|
RS_ERR("File is empty");
|
close(fd);
|
return;
|
}
|
|
config_proc(buff, result);
|
|
close(fd);
|
}
|
#endif
|
|
/* Get the entry from patch_table according to LMP subversion */
|
struct patch_info *get_patch_entry(struct btrtl_info *btrtl)
|
{
|
struct patch_info *n = NULL;
|
|
if (btrtl->proto == HCI_UART_3WIRE)
|
n = patch_table;
|
else
|
n = h4_patch_table;
|
for (; n->lmp_subver; n++) {
|
if ((n->match_flags & RTL_FW_MATCH_CHIP_TYPE) &&
|
n->chip_type != btrtl->chip_type)
|
continue;
|
if ((n->match_flags & RTL_FW_MATCH_HCI_VER) &&
|
n->hci_ver != btrtl->hci_ver)
|
continue;
|
if ((n->match_flags & RTL_FW_MATCH_HCI_REV) &&
|
n->hci_rev != btrtl->hci_rev)
|
continue;
|
if (n->lmp_subver != btrtl->lmp_subver)
|
continue;
|
|
break;
|
}
|
|
return n;
|
}
|
|
// bite reverse in bytes
|
// 00000001 -> 10000000
|
// 00000100 -> 00100000
|
const RT_U8 byte_rev_table[256] = {
|
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
|
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
|
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
|
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
|
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
|
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
|
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
|
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
|
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
|
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
|
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
|
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
|
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
|
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
|
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
|
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
|
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
|
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
|
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
|
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
|
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
|
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
|
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
|
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
|
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
|
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
|
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
|
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
|
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
|
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
|
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
|
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
|
};
|
|
static __inline RT_U8 bit_rev8(RT_U8 byte)
|
{
|
return byte_rev_table[byte];
|
}
|
|
static __inline RT_U16 bit_rev16(RT_U16 x)
|
{
|
return (bit_rev8(x & 0xff) << 8) | bit_rev8(x >> 8);
|
}
|
|
static const RT_U16 crc_table[] = {
|
0x0000, 0x1081, 0x2102, 0x3183,
|
0x4204, 0x5285, 0x6306, 0x7387,
|
0x8408, 0x9489, 0xa50a, 0xb58b,
|
0xc60c, 0xd68d, 0xe70e, 0xf78f
|
};
|
|
// Initialise the crc calculator
|
#define H5_CRC_INIT(x) x = 0xffff
|
|
/**
|
* Malloc the socket buffer
|
*
|
* @param skb socket buffer
|
* @return the point to the malloc buffer
|
*/
|
static __inline struct sk_buff *skb_alloc(unsigned int len)
|
{
|
struct sk_buff *skb = NULL;
|
if ((skb = malloc(len + 8))) {
|
skb->max_len = len;
|
skb->data_len = 0;
|
} else {
|
RS_ERR("Allocate skb fails!!!");
|
skb = NULL;
|
}
|
memset(skb->data, 0, len);
|
return skb;
|
}
|
|
/**
|
* Free the socket buffer
|
*
|
* @param skb socket buffer
|
*/
|
static __inline void skb_free(struct sk_buff *skb)
|
{
|
free(skb);
|
return;
|
}
|
|
/**
|
* Increase the date length in sk_buffer by len,
|
* and return the increased header pointer
|
*
|
* @param skb socket buffer
|
* @param len length want to increase
|
* @return the pointer to increased header
|
*/
|
static RT_U8 *skb_put(struct sk_buff *skb, RT_U32 len)
|
{
|
RT_U32 old_len = skb->data_len;
|
if ((skb->data_len + len) > (skb->max_len)) {
|
RS_ERR("Buffer too small");
|
return NULL;
|
}
|
skb->data_len += len;
|
return (skb->data + old_len);
|
}
|
|
/**
|
* decrease data length in sk_buffer by to len by cut the tail
|
*
|
* @warning len should be less than skb->len
|
*
|
* @param skb socket buffer
|
* @param len length want to be changed
|
*/
|
static void skb_trim(struct sk_buff *skb, unsigned int len)
|
{
|
if (skb->data_len > len) {
|
skb->data_len = len;
|
} else {
|
RS_ERR("Error: skb->data_len(%d) < len(%d)", skb->data_len,
|
len);
|
}
|
}
|
|
/**
|
* Decrease the data length in sk_buffer by len,
|
* and move the content forward to the header.
|
* the data in header will be removed.
|
*
|
* @param skb socket buffer
|
* @param len length of data
|
* @return new data
|
*/
|
static RT_U8 *skb_pull(struct sk_buff *skb, RT_U32 len)
|
{
|
skb->data_len -= len;
|
unsigned char *buf;
|
if (!(buf = malloc(skb->data_len))) {
|
RS_ERR("Unable to allocate file buffer");
|
exit(1);
|
}
|
memcpy(buf, skb->data + len, skb->data_len);
|
memcpy(skb->data, buf, skb->data_len);
|
free(buf);
|
return (skb->data);
|
}
|
|
/**
|
* Add "d" into crc scope, caculate the new crc value
|
*
|
* @param crc crc data
|
* @param d one byte data
|
*/
|
static void h5_crc_update(RT_U16 * crc, RT_U8 d)
|
{
|
RT_U16 reg = *crc;
|
|
reg = (reg >> 4) ^ crc_table[(reg ^ d) & 0x000f];
|
reg = (reg >> 4) ^ crc_table[(reg ^ (d >> 4)) & 0x000f];
|
|
*crc = reg;
|
}
|
|
struct __una_u16 {
|
RT_U16 x;
|
};
|
static __inline RT_U16 __get_unaligned_cpu16(const void *p)
|
{
|
const struct __una_u16 *ptr = (const struct __una_u16 *)p;
|
return ptr->x;
|
}
|
|
static __inline RT_U16 get_unaligned_be16(const void *p)
|
{
|
return __get_unaligned_cpu16((const RT_U8 *)p);
|
}
|
|
static __inline RT_U16 get_unaligned_le16(RT_U8 * p)
|
{
|
return (RT_U16) (*p) + ((RT_U16) (*(p + 1)) << 8);
|
}
|
|
static __inline RT_U32 get_unaligned_le32(RT_U8 * p)
|
{
|
return (RT_U32) (*p) + ((RT_U32) (*(p + 1)) << 8) +
|
((RT_U32) (*(p + 2)) << 16) + ((RT_U32) (*(p + 3)) << 24);
|
}
|
|
/**
|
* Get crc data.
|
*
|
* @param h5 realtek h5 struct
|
* @return crc data
|
*/
|
static RT_U16 h5_get_crc(rtk_hw_cfg_t * h5)
|
{
|
RT_U16 crc = 0;
|
RT_U8 *data = h5->rx_skb->data + h5->rx_skb->data_len - 2;
|
crc = data[1] + (data[0] << 8);
|
return crc;
|
// return get_unaligned_be16(&h5->rx_skb->data[h5->rx_skb->data_len - 2]);
|
}
|
|
/**
|
* Just add 0xc0 at the end of skb,
|
* we can also use this to add 0xc0 at start while there is no data in skb
|
*
|
* @param skb socket buffer
|
*/
|
static void h5_slip_msgdelim(struct sk_buff *skb)
|
{
|
const char pkt_delim = 0xc0;
|
memcpy(skb_put(skb, 1), &pkt_delim, 1);
|
}
|
|
/**
|
* Slip ecode one byte in h5 proto, as follows:
|
* 0xc0 -> 0xdb, 0xdc
|
* 0xdb -> 0xdb, 0xdd
|
* 0x11 -> 0xdb, 0xde
|
* 0x13 -> 0xdb, 0xdf
|
* others will not change
|
*
|
* @param skb socket buffer
|
* @c pure data in the one byte
|
*/
|
static void h5_slip_one_byte(struct sk_buff *skb, RT_U8 c)
|
{
|
const RT_S8 esc_c0[2] = { 0xdb, 0xdc };
|
const RT_S8 esc_db[2] = { 0xdb, 0xdd };
|
const RT_S8 esc_11[2] = { 0xdb, 0xde };
|
const RT_S8 esc_13[2] = { 0xdb, 0xdf };
|
|
switch (c) {
|
case 0xc0:
|
memcpy(skb_put(skb, 2), &esc_c0, 2);
|
break;
|
|
case 0xdb:
|
memcpy(skb_put(skb, 2), &esc_db, 2);
|
break;
|
|
case 0x11:
|
memcpy(skb_put(skb, 2), &esc_11, 2);
|
break;
|
|
case 0x13:
|
memcpy(skb_put(skb, 2), &esc_13, 2);
|
break;
|
|
default:
|
memcpy(skb_put(skb, 1), &c, 1);
|
break;
|
}
|
}
|
|
/**
|
* Decode one byte in h5 proto, as follows:
|
* 0xdb, 0xdc -> 0xc0
|
* 0xdb, 0xdd -> 0xdb
|
* 0xdb, 0xde -> 0x11
|
* 0xdb, 0xdf -> 0x13
|
* others will not change
|
*
|
* @param h5 realtek h5 struct
|
* @byte pure data in the one byte
|
*/
|
static void h5_unslip_one_byte(rtk_hw_cfg_t * h5, unsigned char byte)
|
{
|
const RT_U8 c0 = 0xc0, db = 0xdb;
|
const RT_U8 oof1 = 0x11, oof2 = 0x13;
|
|
if (H5_ESCSTATE_NOESC == h5->rx_esc_state) {
|
if (0xdb == byte) {
|
h5->rx_esc_state = H5_ESCSTATE_ESC;
|
} else {
|
memcpy(skb_put(h5->rx_skb, 1), &byte, 1);
|
//Check Pkt Header's CRC enable bit
|
if ((h5->rx_skb->data[0] & 0x40) != 0
|
&& h5->rx_state != H5_W4_CRC) {
|
h5_crc_update(&h5->message_crc, byte);
|
}
|
h5->rx_count--;
|
}
|
} else if (H5_ESCSTATE_ESC == h5->rx_esc_state) {
|
switch (byte) {
|
case 0xdc:
|
memcpy(skb_put(h5->rx_skb, 1), &c0, 1);
|
if ((h5->rx_skb->data[0] & 0x40) != 0
|
&& h5->rx_state != H5_W4_CRC)
|
h5_crc_update(&h5->message_crc, 0xc0);
|
h5->rx_esc_state = H5_ESCSTATE_NOESC;
|
h5->rx_count--;
|
break;
|
|
case 0xdd:
|
memcpy(skb_put(h5->rx_skb, 1), &db, 1);
|
if ((h5->rx_skb->data[0] & 0x40) != 0
|
&& h5->rx_state != H5_W4_CRC)
|
h5_crc_update(&h5->message_crc, 0xdb);
|
h5->rx_esc_state = H5_ESCSTATE_NOESC;
|
h5->rx_count--;
|
break;
|
|
case 0xde:
|
memcpy(skb_put(h5->rx_skb, 1), &oof1, 1);
|
if ((h5->rx_skb->data[0] & 0x40) != 0
|
&& h5->rx_state != H5_W4_CRC)
|
h5_crc_update(&h5->message_crc, oof1);
|
h5->rx_esc_state = H5_ESCSTATE_NOESC;
|
h5->rx_count--;
|
break;
|
|
case 0xdf:
|
memcpy(skb_put(h5->rx_skb, 1), &oof2, 1);
|
if ((h5->rx_skb->data[0] & 0x40) != 0
|
&& h5->rx_state != H5_W4_CRC)
|
h5_crc_update(&h5->message_crc, oof2);
|
h5->rx_esc_state = H5_ESCSTATE_NOESC;
|
h5->rx_count--;
|
break;
|
|
default:
|
RS_ERR("Error: Invalid byte %02x after esc byte", byte);
|
skb_free(h5->rx_skb);
|
h5->rx_skb = NULL;
|
h5->rx_state = H5_W4_PKT_DELIMITER;
|
h5->rx_count = 0;
|
break;
|
}
|
}
|
}
|
|
/**
|
* Prepare h5 packet, packet format as follow:
|
* | LSB 4 octets | 0 ~4095| 2 MSB
|
* |packet header | payload | data integrity check |
|
*
|
* pakcket header fromat is show below:
|
* | LSB 3 bits | 3 bits | 1 bits | 1 bits |
|
* | 4 bits | 12 bits | 8 bits MSB
|
* |sequence number | acknowledgement number | data integrity check present | reliable packet |
|
* |packet type | payload length | header checksum
|
*
|
* @param h5 realtek h5 struct
|
* @param data pure data
|
* @param len the length of data
|
* @param pkt_type packet type
|
* @return socket buff after prepare in h5 proto
|
*/
|
static struct sk_buff *h5_prepare_pkt(rtk_hw_cfg_t * h5, RT_U8 * data,
|
RT_S32 len, RT_S32 pkt_type)
|
{
|
struct sk_buff *nskb;
|
RT_U8 hdr[4];
|
RT_U16 H5_CRC_INIT(h5_txmsg_crc);
|
int rel, i;
|
|
switch (pkt_type) {
|
case HCI_ACLDATA_PKT:
|
case HCI_COMMAND_PKT:
|
case HCI_EVENT_PKT:
|
rel = 1; // reliable
|
break;
|
|
case H5_ACK_PKT:
|
case H5_VDRSPEC_PKT:
|
case H5_LINK_CTL_PKT:
|
rel = 0; // unreliable
|
break;
|
|
default:
|
RS_ERR("Unknown packet type");
|
return NULL;
|
}
|
|
// Max len of packet: (original len +4(h5 hdr) +2(crc))*2
|
// (because bytes 0xc0 and 0xdb are escaped, worst case is
|
// when the packet is all made of 0xc0 and 0xdb :) )
|
// + 2 (0xc0 delimiters at start and end).
|
|
nskb = skb_alloc((len + 6) * 2 + 2);
|
if (!nskb)
|
return NULL;
|
|
//Add SLIP start byte: 0xc0
|
h5_slip_msgdelim(nskb);
|
// set AckNumber in SlipHeader
|
hdr[0] = h5->rxseq_txack << 3;
|
h5->is_txack_req = 0;
|
|
//RS_DBG("We request packet no(%u) to card", h5->rxseq_txack);
|
//RS_DBG("Sending packet with seqno %u and wait %u", h5->msgq_txseq, h5->rxseq_txack);
|
if (rel) {
|
// set reliable pkt bit and SeqNumber
|
hdr[0] |= 0x80 + h5->msgq_txseq;
|
//RS_DBG("Sending packet with seqno(%u)", h5->msgq_txseq);
|
++(h5->msgq_txseq);
|
h5->msgq_txseq = (h5->msgq_txseq) & 0x07;
|
}
|
// set DicPresent bit
|
if (h5->use_crc)
|
hdr[0] |= 0x40;
|
|
// set packet type and payload length
|
hdr[1] = ((len << 4) & 0xff) | pkt_type;
|
hdr[2] = (RT_U8) (len >> 4);
|
// set checksum
|
hdr[3] = ~(hdr[0] + hdr[1] + hdr[2]);
|
|
// Put h5 header */
|
for (i = 0; i < 4; i++) {
|
h5_slip_one_byte(nskb, hdr[i]);
|
|
if (h5->use_crc)
|
h5_crc_update(&h5_txmsg_crc, hdr[i]);
|
}
|
|
// Put payload */
|
for (i = 0; i < len; i++) {
|
h5_slip_one_byte(nskb, data[i]);
|
|
if (h5->use_crc)
|
h5_crc_update(&h5_txmsg_crc, data[i]);
|
}
|
|
// Put CRC */
|
if (h5->use_crc) {
|
h5_txmsg_crc = bit_rev16(h5_txmsg_crc);
|
h5_slip_one_byte(nskb, (RT_U8) ((h5_txmsg_crc >> 8) & 0x00ff));
|
h5_slip_one_byte(nskb, (RT_U8) (h5_txmsg_crc & 0x00ff));
|
}
|
// Add SLIP end byte: 0xc0
|
h5_slip_msgdelim(nskb);
|
return nskb;
|
}
|
|
/**
|
* Removed controller acked packet from Host's unacked lists
|
*
|
* @param h5 realtek h5 struct
|
*/
|
static void h5_remove_acked_pkt(rtk_hw_cfg_t * h5)
|
{
|
int pkts_to_be_removed = 0;
|
int seqno = 0;
|
int i = 0;
|
|
seqno = h5->msgq_txseq;
|
//pkts_to_be_removed = GetListLength(h5->unacked);
|
|
while (pkts_to_be_removed) {
|
if (h5->rxack == seqno)
|
break;
|
|
pkts_to_be_removed--;
|
seqno = (seqno - 1) & 0x07;
|
}
|
|
if (h5->rxack != seqno) {
|
RS_DBG("Peer acked invalid packet");
|
}
|
//skb_queue_walk_safe(&h5->unack, skb, tmp) // remove ack'ed packet from h5->unack queue
|
for (i = 0; i < 5; ++i) {
|
if (i >= pkts_to_be_removed)
|
break;
|
i++;
|
//__skb_unlink(skb, &h5->unack);
|
//skb_free(skb);
|
}
|
|
// if (skb_queue_empty(&h5->unack))
|
// del_timer(&h5->th5);
|
// spin_unlock_irqrestore(&h5->unack.lock, flags);
|
|
if (i != pkts_to_be_removed)
|
RS_DBG("Removed only (%u) out of (%u) pkts", i,
|
pkts_to_be_removed);
|
}
|
|
/**
|
* Realtek send pure ack, send a packet only with an ack
|
*
|
* @param fd uart file descriptor
|
*
|
*/
|
static void rtk_send_pure_ack_down(int fd)
|
{
|
struct sk_buff *nskb = h5_prepare_pkt(&rtk_hw_cfg, NULL, 0, H5_ACK_PKT);
|
write(fd, nskb->data, nskb->data_len);
|
skb_free(nskb);
|
return;
|
}
|
|
/**
|
* Parse hci event command complete, pull the cmd complete event header
|
*
|
* @param skb socket buffer
|
*
|
*/
|
static void hci_event_cmd_complete(struct sk_buff *skb)
|
{
|
struct hci_event_hdr *hdr = (struct hci_event_hdr *)skb->data;
|
struct hci_ev_cmd_complete *ev = NULL;
|
RT_U16 opcode = 0;
|
RT_U8 status = 0;
|
|
//pull hdr
|
skb_pull(skb, HCI_EVENT_HDR_SIZE);
|
ev = (struct hci_ev_cmd_complete *)skb->data;
|
opcode = le16_to_cpu(ev->opcode);
|
|
RS_DBG("receive hci command complete event with command:%x\n", opcode);
|
|
//pull command complete event header
|
skb_pull(skb, sizeof(struct hci_ev_cmd_complete));
|
|
switch (opcode) {
|
case HCI_VENDOR_CHANGE_BDRATE:
|
status = skb->data[0];
|
RS_DBG("Change BD Rate with status:%x", status);
|
skb_free(rtk_hw_cfg.host_last_cmd);
|
rtk_hw_cfg.host_last_cmd = NULL;
|
rtk_hw_cfg.link_estab_state = H5_PATCH;
|
break;
|
|
case HCI_CMD_READ_BD_ADDR:
|
status = skb->data[0];
|
RS_DBG("Read BD Address with Status:%x", status);
|
if (!status) {
|
RS_DBG("BD Address: %8x%8x", *(int *)&skb->data[1],
|
*(int *)&skb->data[5]);
|
}
|
break;
|
|
case HCI_CMD_READ_LOCAL_VERISION:
|
rtk_hw_cfg.hci_version_cmd_state = event_received;
|
status = skb->data[0];
|
RS_DBG("Read Local Version Information with Status:%x", status);
|
if (0 == status) {
|
rtk_hw_cfg.hci_ver = skb->data[1];
|
rtk_hw_cfg.hci_rev = (skb->data[2] | skb->data[3] << 8);
|
rtk_hw_cfg.lmp_subver =
|
(skb->data[7] | (skb->data[8] << 8));
|
RS_DBG("HCI Version 0x%02x", rtk_hw_cfg.hci_ver);
|
RS_DBG("HCI Revision 0x%04x", rtk_hw_cfg.hci_rev);
|
RS_DBG("LMP Subversion 0x%04x", rtk_hw_cfg.lmp_subver);
|
} else {
|
RS_ERR("Read Local Version Info status error!");
|
//Need to do more
|
}
|
skb_free(rtk_hw_cfg.host_last_cmd);
|
rtk_hw_cfg.host_last_cmd = NULL;
|
break;
|
|
case HCI_VENDOR_READ_RTK_ROM_VERISION:
|
rtk_hw_cfg.rom_version_cmd_state = event_received;
|
status = skb->data[0];
|
RS_DBG("Read RTK rom version with Status:%x", status);
|
if (0 == status)
|
rtk_hw_cfg.eversion = skb->data[1];
|
else if (1 == status)
|
rtk_hw_cfg.eversion = 0;
|
else {
|
RS_ERR("READ_RTK_ROM_VERISION return status error!");
|
//Need to do more
|
}
|
|
skb_free(rtk_hw_cfg.host_last_cmd);
|
rtk_hw_cfg.host_last_cmd = NULL;
|
break;
|
case HCI_VENDOR_READ_CHIP_TYPE:
|
rtk_hw_cfg.chip_type_cmd_state = event_received;
|
status = skb->data[0];
|
RS_DBG("Read RTK chip type with Status:%x", status);
|
if (0 == status)
|
rtk_hw_cfg.chip_type= (skb->data[1] & 0x0f);
|
else
|
RS_ERR("READ_RTK_CHIP_TYPE return status error!");
|
skb_free(rtk_hw_cfg.host_last_cmd);
|
rtk_hw_cfg.host_last_cmd = NULL;
|
break;
|
default:
|
return;
|
}
|
rtk_hw_cfg.num_of_cmd_sent++;
|
}
|
|
/**
|
* Check if it's a hci frame, if it is, complete it with response or parse the cmd complete event
|
*
|
* @param skb socket buffer
|
*
|
*/
|
static void hci_recv_frame(struct sk_buff *skb)
|
{
|
int len;
|
unsigned char h5sync[2] = { 0x01, 0x7E }, h5syncresp[2] = {
|
0x02, 0x7D}, h5_sync_resp_pkt[0x8] = {
|
0xc0, 0x00, 0x2F, 0x00, 0xD0, 0x02, 0x7D, 0xc0},
|
h5_conf_resp_pkt_to_Ctrl[0x8] = {
|
0xc0, 0x00, 0x2F, 0x00, 0xD0, 0x04, 0x7B, 0xc0}, h5conf[3] = {
|
0x03, 0xFC, 0x10}, h5confresp[3] = {
|
0x04, 0x7B, 0x10}, cmd_complete_evt_code = 0xe;
|
|
if (rtk_hw_cfg.link_estab_state == H5_SYNC) {
|
if (!memcmp(skb->data, h5sync, 2)) {
|
RS_DBG("Get SYNC Pkt\n");
|
len =
|
write(rtk_hw_cfg.serial_fd, &h5_sync_resp_pkt, 0x8);
|
} else if (!memcmp(skb->data, h5syncresp, 2)) {
|
RS_DBG("Get SYNC Resp Pkt\n");
|
rtk_hw_cfg.link_estab_state = H5_CONFIG;
|
}
|
skb_free(skb);
|
} else if (rtk_hw_cfg.link_estab_state == H5_CONFIG) {
|
if (!memcmp(skb->data, h5sync, 0x2)) {
|
len =
|
write(rtk_hw_cfg.serial_fd, &h5_sync_resp_pkt, 0x8);
|
RS_DBG("Get SYNC pkt-active mode\n");
|
} else if (!memcmp(skb->data, h5conf, 0x2)) {
|
len =
|
write(rtk_hw_cfg.serial_fd,
|
&h5_conf_resp_pkt_to_Ctrl, 0x8);
|
RS_DBG("Get CONFG pkt-active mode\n");
|
} else if (!memcmp(skb->data, h5confresp, 0x2)) {
|
RS_DBG("Get CONFG resp pkt-active mode\n");
|
rtk_hw_cfg.link_estab_state = H5_INIT;
|
} else {
|
RS_DBG("H5_CONFIG receive event\n");
|
rtk_send_pure_ack_down(rtk_hw_cfg.serial_fd);
|
}
|
skb_free(skb);
|
} else if (rtk_hw_cfg.link_estab_state == H5_INIT) {
|
if (skb->data[0] == cmd_complete_evt_code) {
|
hci_event_cmd_complete(skb);
|
}
|
|
rtk_send_pure_ack_down(rtk_hw_cfg.serial_fd);
|
usleep(10000);
|
rtk_send_pure_ack_down(rtk_hw_cfg.serial_fd);
|
usleep(10000);
|
rtk_send_pure_ack_down(rtk_hw_cfg.serial_fd);
|
skb_free(skb);
|
} else if (rtk_hw_cfg.link_estab_state == H5_PATCH) {
|
if (skb->data[0] != 0x0e) {
|
RS_DBG("Received event 0x%x\n", skb->data[0]);
|
skb_free(skb);
|
rtk_send_pure_ack_down(rtk_hw_cfg.serial_fd);
|
return;
|
}
|
|
rtk_hw_cfg.rx_index = skb->data[6];
|
|
RS_DBG("rtk_hw_cfg.rx_index %d\n", rtk_hw_cfg.rx_index);
|
|
/* Download fw/config done */
|
if (rtk_hw_cfg.rx_index & 0x80) {
|
rtk_hw_cfg.rx_index &= ~0x80;
|
rtk_hw_cfg.link_estab_state = H5_ACTIVE;
|
}
|
|
skb_free(skb);
|
} else {
|
RS_ERR("receive packets in active state");
|
skb_free(skb);
|
}
|
}
|
|
/**
|
* after rx data is parsed, and we got a rx frame saved in h5->rx_skb,
|
* this routinue is called.
|
* things todo in this function:
|
* 1. check if it's a hci frame, if it is, complete it with response or ack
|
* 2. see the ack number, free acked frame in queue
|
* 3. reset h5->rx_state, set rx_skb to null.
|
*
|
* @param h5 realtek h5 struct
|
*
|
*/
|
static void h5_complete_rx_pkt(rtk_hw_cfg_t * h5)
|
{
|
int pass_up = 1;
|
uint8_t *h5_hdr = NULL;
|
|
h5_hdr = (uint8_t *) (h5->rx_skb->data);
|
if (H5_HDR_RELIABLE(h5_hdr)) {
|
RS_DBG("Received reliable seqno %u from card", h5->rxseq_txack);
|
h5->rxseq_txack = H5_HDR_SEQ(h5_hdr) + 1;
|
h5->rxseq_txack %= 8;
|
h5->is_txack_req = 1;
|
}
|
|
h5->rxack = H5_HDR_ACK(h5_hdr);
|
|
switch (H5_HDR_PKT_TYPE(h5_hdr)) {
|
case HCI_ACLDATA_PKT:
|
case HCI_EVENT_PKT:
|
case HCI_SCODATA_PKT:
|
case HCI_COMMAND_PKT:
|
case H5_LINK_CTL_PKT:
|
pass_up = 1;
|
break;
|
|
default:
|
pass_up = 0;
|
break;
|
}
|
|
h5_remove_acked_pkt(h5);
|
|
if (pass_up) {
|
skb_pull(h5->rx_skb, H5_HDR_SIZE);
|
hci_recv_frame(h5->rx_skb);
|
} else {
|
skb_free(h5->rx_skb);
|
}
|
|
h5->rx_state = H5_W4_PKT_DELIMITER;
|
h5->rx_skb = NULL;
|
}
|
|
/**
|
* Parse the receive data in h5 proto.
|
*
|
* @param h5 realtek h5 struct
|
* @param data point to data received before parse
|
* @param count num of data
|
* @return reserved count
|
*/
|
static int h5_recv(rtk_hw_cfg_t * h5, void *data, int count)
|
{
|
unsigned char *ptr;
|
//RS_DBG("count %d rx_state %d rx_count %ld", count, h5->rx_state, h5->rx_count);
|
ptr = (unsigned char *)data;
|
|
while (count) {
|
if (h5->rx_count) {
|
if (*ptr == 0xc0) {
|
RS_ERR("short h5 packet");
|
skb_free(h5->rx_skb);
|
h5->rx_state = H5_W4_PKT_START;
|
h5->rx_count = 0;
|
} else
|
h5_unslip_one_byte(h5, *ptr);
|
|
ptr++;
|
count--;
|
continue;
|
}
|
|
switch (h5->rx_state) {
|
case H5_W4_HDR:
|
/* check header checksum. see Core Spec V4 "3-wire uart" page 67 */
|
if ((0xff & (RT_U8) ~
|
(h5->rx_skb->data[0] + h5->rx_skb->data[1] +
|
h5->rx_skb->data[2])) != h5->rx_skb->data[3]) {
|
RS_ERR("h5 hdr checksum error!!!");
|
skb_free(h5->rx_skb);
|
h5->rx_state = H5_W4_PKT_DELIMITER;
|
h5->rx_count = 0;
|
continue;
|
}
|
|
/* reliable pkt & h5->hdr->SeqNumber != h5->Rxseq_txack */
|
if (h5->rx_skb->data[0] & 0x80
|
&& (h5->rx_skb->data[0] & 0x07) !=
|
h5->rxseq_txack) {
|
RS_ERR
|
("Out-of-order packet arrived, got(%u)expected(%u)",
|
h5->rx_skb->data[0] & 0x07,
|
h5->rxseq_txack);
|
h5->is_txack_req = 1;
|
|
skb_free(h5->rx_skb);
|
h5->rx_state = H5_W4_PKT_DELIMITER;
|
h5->rx_count = 0;
|
|
/* depend on weather remote will reset ack numb or not!!!!!!special */
|
if (rtk_hw_cfg.tx_index == rtk_hw_cfg.total_num) {
|
rtk_hw_cfg.rxseq_txack =
|
h5->rx_skb->data[0] & 0x07;
|
}
|
continue;
|
}
|
h5->rx_state = H5_W4_DATA;
|
h5->rx_count =
|
(h5->rx_skb->data[1] >> 4) +
|
(h5->rx_skb->data[2] << 4);
|
continue;
|
|
case H5_W4_DATA:
|
/* pkt with crc */
|
if (h5->rx_skb->data[0] & 0x40) {
|
h5->rx_state = H5_W4_CRC;
|
h5->rx_count = 2;
|
} else {
|
h5_complete_rx_pkt(h5);
|
//RS_DBG(DF_SLIP,("--------> H5_W4_DATA ACK\n"));
|
}
|
continue;
|
|
case H5_W4_CRC:
|
if (bit_rev16(h5->message_crc) != h5_get_crc(h5)) {
|
RS_ERR
|
("Checksum failed, computed(%04x)received(%04x)",
|
bit_rev16(h5->message_crc),
|
h5_get_crc(h5));
|
skb_free(h5->rx_skb);
|
h5->rx_state = H5_W4_PKT_DELIMITER;
|
h5->rx_count = 0;
|
continue;
|
}
|
skb_trim(h5->rx_skb, h5->rx_skb->data_len - 2);
|
h5_complete_rx_pkt(h5);
|
continue;
|
|
case H5_W4_PKT_DELIMITER:
|
switch (*ptr) {
|
case 0xc0:
|
h5->rx_state = H5_W4_PKT_START;
|
break;
|
|
default:
|
break;
|
}
|
ptr++;
|
count--;
|
break;
|
|
case H5_W4_PKT_START:
|
switch (*ptr) {
|
case 0xc0:
|
ptr++;
|
count--;
|
break;
|
|
default:
|
h5->rx_state = H5_W4_HDR;
|
h5->rx_count = 4;
|
h5->rx_esc_state = H5_ESCSTATE_NOESC;
|
H5_CRC_INIT(h5->message_crc);
|
|
// Do not increment ptr or decrement count
|
// Allocate packet. Max len of a H5 pkt=
|
// 0xFFF (payload) +4 (header) +2 (crc)
|
h5->rx_skb = skb_alloc(0x1005);
|
if (!h5->rx_skb) {
|
h5->rx_state = H5_W4_PKT_DELIMITER;
|
h5->rx_count = 0;
|
return 0;
|
}
|
break;
|
}
|
break;
|
|
default:
|
break;
|
}
|
}
|
return count;
|
}
|
|
/**
|
* Read data to buf from uart.
|
*
|
* @param fd uart file descriptor
|
* @param buf point to the addr where read data stored
|
* @param count num of data want to read
|
* @return num of data successfully read
|
*/
|
static int read_check_rtk(int fd, void *buf, int count)
|
{
|
int res;
|
do {
|
res = read(fd, buf, count);
|
if (res != -1) {
|
buf = (RT_U8 *) buf + res;
|
count -= res;
|
return res;
|
}
|
} while (count && (errno == 0 || errno == EINTR));
|
return res;
|
}
|
|
/**
|
* Retry to sync when timeout in h5 proto, max retry times is 10.
|
*
|
* @warning Each time to retry, the time for timeout will be set as 1s.
|
*
|
* @param sig signaction for timeout
|
*
|
*/
|
static void h5_tsync_sig_alarm(int sig)
|
{
|
unsigned char h5sync[2] = { 0x01, 0x7E };
|
static int retries = 0;
|
struct itimerval value;
|
|
if (retries < rtk_hw_cfg.h5_max_retries) {
|
retries++;
|
struct sk_buff *nskb =
|
h5_prepare_pkt(&rtk_hw_cfg, h5sync, sizeof(h5sync),
|
H5_LINK_CTL_PKT);
|
int len =
|
write(rtk_hw_cfg.serial_fd, nskb->data, nskb->data_len);
|
RS_DBG("3-wire sync pattern resend : %d, len: %d\n", retries,
|
len);
|
|
skb_free(nskb);
|
//gordon add 2013-6-7 retry per 250ms
|
value.it_value.tv_sec = 0;
|
value.it_value.tv_usec = 250000;
|
value.it_interval.tv_sec = 0;
|
value.it_interval.tv_usec = 250000;
|
setitimer(ITIMER_REAL, &value, NULL);
|
//gordon end
|
|
return;
|
}
|
|
tcflush(rtk_hw_cfg.serial_fd, TCIOFLUSH);
|
RS_ERR("H5 sync timed out\n");
|
exit(1);
|
}
|
|
/**
|
* Retry to config when timeout in h5 proto, max retry times is 10.
|
*
|
* @warning Each time to retry, the time for timeout will be set as 1s.
|
*
|
* @param sig signaction for timeout
|
*
|
*/
|
static void h5_tconf_sig_alarm(int sig)
|
{
|
unsigned char h5conf[3] = { 0x03, 0xFC, 0x14 };
|
static int retries = 0;
|
struct itimerval value;
|
|
if (retries < rtk_hw_cfg.h5_max_retries) {
|
retries++;
|
struct sk_buff *nskb =
|
h5_prepare_pkt(&rtk_hw_cfg, h5conf, 3, H5_LINK_CTL_PKT);
|
int len =
|
write(rtk_hw_cfg.serial_fd, nskb->data, nskb->data_len);
|
RS_DBG("3-wire config pattern resend : %d , len: %d", retries,
|
len);
|
skb_free(nskb);
|
|
//gordon add 2013-6-7 retry per 250ms
|
value.it_value.tv_sec = 0;
|
value.it_value.tv_usec = 250000;
|
value.it_interval.tv_sec = 0;
|
value.it_interval.tv_usec = 250000;
|
setitimer(ITIMER_REAL, &value, NULL);
|
|
return;
|
}
|
|
tcflush(rtk_hw_cfg.serial_fd, TCIOFLUSH);
|
RS_ERR("H5 config timed out\n");
|
exit(1);
|
}
|
|
/**
|
* Retry to init when timeout in h5 proto, max retry times is 10.
|
*
|
* @warning Each time to retry, the time for timeout will be set as 1s.
|
*
|
* @param sig signaction for timeout
|
*
|
*/
|
static void h5_tinit_sig_alarm(int sig)
|
{
|
static int retries = 0;
|
if (retries < rtk_hw_cfg.h5_max_retries) {
|
retries++;
|
if (rtk_hw_cfg.host_last_cmd) {
|
int len =
|
write(rtk_hw_cfg.serial_fd,
|
rtk_hw_cfg.host_last_cmd->data,
|
rtk_hw_cfg.host_last_cmd->data_len);
|
RS_DBG("3-wire change baudrate re send:%d, len:%d",
|
retries, len);
|
alarm(1);
|
return;
|
} else {
|
RS_DBG
|
("3-wire init timeout without last command stored\n");
|
}
|
}
|
|
tcflush(rtk_hw_cfg.serial_fd, TCIOFLUSH);
|
RS_ERR("H5 init process timed out");
|
exit(1);
|
}
|
|
/**
|
* Retry to download patch when timeout in h5 proto, max retry times is 10.
|
*
|
* @warning Each time to retry, the time for timeout will be set as 3s.
|
*
|
* @param sig signaction for timeout
|
*
|
*/
|
static void h5_tpatch_sig_alarm(int sig)
|
{
|
static int retries = 0;
|
if (retries < rtk_hw_cfg.h5_max_retries) {
|
RS_ERR("patch timerout, retry:\n");
|
if (rtk_hw_cfg.host_last_cmd) {
|
int len =
|
write(rtk_hw_cfg.serial_fd,
|
rtk_hw_cfg.host_last_cmd->data,
|
rtk_hw_cfg.host_last_cmd->data_len);
|
RS_DBG("3-wire download patch re send:%d", retries);
|
}
|
retries++;
|
alarm(3);
|
return;
|
}
|
RS_ERR("H5 patch timed out\n");
|
exit(1);
|
}
|
|
/**
|
* Download patch using hci. For h5 proto, not recv reply for 2s will timeout.
|
* Call h5_tpatch_sig_alarm for retry.
|
*
|
* @param dd uart file descriptor
|
* @param index current index
|
* @param data point to the config file
|
* @param len current buf length
|
* @return #0 on success
|
*
|
*/
|
static int hci_download_patch(int dd, int index, uint8_t * data, int len,
|
struct termios *ti)
|
{
|
unsigned char hcipatch[256] = { 0x20, 0xfc, 00 };
|
unsigned char bytes[READ_DATA_SIZE];
|
int retlen;
|
struct sigaction sa;
|
|
sa.sa_handler = h5_tpatch_sig_alarm;
|
sigaction(SIGALRM, &sa, NULL);
|
alarm(2);
|
|
download_vendor_patch_cp cp;
|
memset(&cp, 0, sizeof(cp));
|
cp.index = index;
|
if (data != NULL) {
|
memcpy(cp.data, data, len);
|
}
|
|
if (index & 0x80)
|
rtk_hw_cfg.tx_index = index & 0x7f;
|
else
|
rtk_hw_cfg.tx_index = index;
|
|
hcipatch[2] = len + 1;
|
memcpy(hcipatch + 3, &cp, len + 1);
|
|
struct sk_buff *nskb = h5_prepare_pkt(&rtk_hw_cfg, hcipatch, len + 4, HCI_COMMAND_PKT); //data:len+head:4
|
|
if (rtk_hw_cfg.host_last_cmd) {
|
skb_free(rtk_hw_cfg.host_last_cmd);
|
rtk_hw_cfg.host_last_cmd = NULL;
|
}
|
|
rtk_hw_cfg.host_last_cmd = nskb;
|
|
len = write(dd, nskb->data, nskb->data_len);
|
RS_DBG("hci_download_patch tx_index:%d rx_index: %d\n",
|
rtk_hw_cfg.tx_index, rtk_hw_cfg.rx_index);
|
|
while (rtk_hw_cfg.rx_index != rtk_hw_cfg.tx_index) { //receive data and wait last pkt
|
if ((retlen = read_check_rtk(dd, &bytes, READ_DATA_SIZE)) == -1) {
|
RS_ERR("read fail\n");
|
return -1;
|
}
|
h5_recv(&rtk_hw_cfg, &bytes, retlen);
|
}
|
|
alarm(0);
|
|
return 0;
|
}
|
|
#define READ_TRY_MAX 6
|
int os_read(int fd, uint8_t * buff, int len)
|
{
|
int n;
|
int i;
|
int try = 0;
|
|
i = 0;
|
n = 0;
|
while (n < len) {
|
i = read(fd, buff + n, len - n);
|
if (i > 0)
|
n += i;
|
else if (i == 0) {
|
RS_DBG("read nothing.");
|
continue;
|
} else {
|
RS_ERR("read error, %s\n", strerror(errno));
|
try++;
|
if (try > READ_TRY_MAX) {
|
RS_ERR("read reaches max try number.\n");
|
return -1;
|
}
|
continue;
|
}
|
}
|
|
return n;
|
}
|
|
#define DUMP_HCI_EVT
|
#ifdef DUMP_HCI_EVT
|
#define HCI_DUMP_BUF_LEN 128
|
static char hci_dump_buf[HCI_DUMP_BUF_LEN];
|
void hci_dump_evt(uint8_t * buf, uint16_t len)
|
{
|
int n;
|
int i;
|
|
if (!buf || !len) {
|
RS_ERR("Invalid parameters %p, %u.\n", buf, len);
|
return;
|
}
|
|
n = 0;
|
for (i = 0; i < len; i++) {
|
n += sprintf(hci_dump_buf + n, "%02x ", buf[i]);
|
if ((i + 1) % 16 == 0) {
|
RS_DBG(" %s\n", hci_dump_buf);
|
n = 0;
|
}
|
}
|
|
if (i % 16)
|
RS_DBG(" %s\n", hci_dump_buf);
|
}
|
#endif
|
|
int read_hci_evt(int fd, uint8_t * buff, uint8_t evt_code)
|
{
|
uint8_t *evt_buff = buff;
|
int ret;
|
int try_type = 0;
|
uint8_t vendor_evt = 0xff;
|
|
start_read:
|
do {
|
ret = os_read(fd, evt_buff, 1);
|
if (ret == 1 && evt_buff[0] == 0x04)
|
break;
|
else {
|
RS_DBG("no pkt type, continue.");
|
try_type++;
|
continue;
|
}
|
} while (try_type < 6);
|
|
if (try_type >= 6)
|
return -1;
|
|
ret = os_read(fd, evt_buff + 1, 1);
|
if (ret < 0) {
|
RS_ERR("%s: failed to read event code\n", __func__);
|
return -1;
|
}
|
|
ret = os_read(fd, evt_buff + 2, 1);
|
if (ret < 0) {
|
RS_ERR("%s: failed to read parameter total len.\n", __func__);
|
return -1;
|
}
|
|
ret = os_read(fd, evt_buff + 3, evt_buff[2]);
|
if (ret < 0) {
|
RS_ERR("%s: failed to read payload of event.\n", __func__);
|
return -1;
|
}
|
#ifdef DUMP_HCI_EVT
|
hci_dump_evt(evt_buff, ret + 3);
|
#endif
|
|
/* This event to wake up host. */
|
if (evt_buff[1] == vendor_evt) {
|
try_type = 0;
|
RS_DBG("%s: found vendor evt, continue reading.\n", __func__);
|
goto start_read;
|
}
|
|
if (evt_buff[1] != evt_code) {
|
RS_ERR("%s: event code mismatches, %x, expect %x.\n",
|
__func__, evt_buff[1], evt_code);
|
return -1;
|
}
|
|
return (ret + 3);
|
}
|
|
/**
|
* Download h4 patch
|
*
|
* @param dd uart file descriptor
|
* @param index current index
|
* @param data point to the config file
|
* @param len current buf length
|
* @return ret_index
|
*
|
*/
|
static int hci_download_patch_h4(int dd, int index, uint8_t * data, int len)
|
{
|
unsigned char bytes[257] = { 0 };
|
unsigned char buf[257] = { 0x01, 0x20, 0xfc, 00 };
|
uint16_t readbytes = 0;
|
int cur_index = index;
|
int ret_Index = -1;
|
uint16_t res = 0;
|
int i = 0;
|
size_t total_len;
|
uint16_t w_len;
|
uint8_t rstatus;
|
int ret;
|
uint8_t opcode[2] = {
|
0x20, 0xfc,
|
};
|
|
RS_DBG("dd:%d, index:%d, len:%d", dd, index, len);
|
if (NULL != data) {
|
memcpy(&buf[5], data, len);
|
}
|
|
buf[3] = len + 1;
|
buf[4] = cur_index;
|
total_len = len + 5;
|
|
w_len = write(dd, buf, total_len);
|
RS_DBG("h4 write success with len: %d\n", w_len);
|
|
ret = read_hci_evt(dd, bytes, 0x0e);
|
if (ret < 0) {
|
RS_ERR("%s: read hci evt error.\n", __func__);
|
return -1;
|
}
|
|
/* RS_DBG("%s: bytes: %x %x %x %x %x %x.\n",
|
* __func__, bytes[0], bytes[1], bytes[2],
|
* bytes[3], bytes[4], bytes[5]); */
|
|
if ((0x04 == bytes[0]) && (opcode[0] == bytes[4])
|
&& (opcode[1] == bytes[5])) {
|
ret_Index = bytes[7];
|
rstatus = bytes[6];
|
RS_DBG("---->ret_Index:%d, ----->rstatus:%d\n", ret_Index,
|
rstatus);
|
if (0x00 != rstatus) {
|
RS_ERR("---->read event status is wrong\n");
|
return -1;
|
}
|
} else {
|
RS_ERR("==========>Didn't read curret data\n");
|
return -1;
|
}
|
|
return ret_Index;
|
}
|
|
/**
|
* Realtek change speed with h4 proto. Using vendor specified command packet to achieve this.
|
*
|
* @warning before write, need to wait 1s for device up
|
*
|
* @param fd uart file descriptor
|
* @param baudrate the speed want to change
|
* @return #0 on success
|
*/
|
static int rtk_vendor_change_speed_h4(int fd, RT_U32 baudrate)
|
{
|
int res;
|
unsigned char bytes[257];
|
RT_U8 cmd[8] = { 0 };
|
|
cmd[0] = 1; //cmd;
|
cmd[1] = 0x17; //ocf
|
cmd[2] = 0xfc; //ogf
|
cmd[3] = 4; //length;
|
|
baudrate = cpu_to_le32(baudrate);
|
#ifdef BAUDRATE_4BYTES
|
memcpy((RT_U16 *) & cmd[4], &baudrate, 4);
|
#else
|
memcpy((RT_U16 *) & cmd[4], &baudrate, 2);
|
cmd[6] = 0;
|
cmd[7] = 0;
|
#endif
|
|
//wait for a while for device to up, just h4 need it
|
sleep(1);
|
RS_DBG("baudrate in change speed command: 0x%x 0x%x 0x%x 0x%x \n",
|
cmd[4], cmd[5], cmd[6], cmd[7]);
|
|
if (write(fd, cmd, 8) != 8) {
|
RS_ERR
|
("H4 change uart speed error when writing vendor command");
|
return -1;
|
}
|
RS_DBG("H4 Change uart Baudrate after write ");
|
|
res = read_hci_evt(fd, bytes, 0x0e);
|
if (res < 0) {
|
RS_ERR("%s: Failed to read hci evt.\n", __func__);
|
return -1;
|
}
|
|
if ((0x04 == bytes[0]) && (0x17 == bytes[4]) && (0xfc == bytes[5])) {
|
RS_DBG("H4 change uart speed success, receving status:%x",
|
bytes[6]);
|
if (bytes[6] == 0)
|
return 0;
|
}
|
return -1;
|
}
|
|
/**
|
* Parse realtek Bluetooth config file.
|
* The config file if begin with vendor magic: RTK_VENDOR_CONFIG_MAGIC(8723ab55)
|
* bt_addr is followed by 0x3c offset, it will be changed by bt_addr param
|
* proto, baudrate and flow control is followed by 0xc offset,
|
*
|
* @param config_buf point to config file content
|
* @param *plen length of config file
|
* @param bt_addr where bt addr is stored
|
* @return
|
* 1. new config buf
|
* 2. new config length *plen
|
* 3. result (for baudrate)
|
*
|
*/
|
|
uint8_t *rtk_parse_config_file(RT_U8 *config_buf, size_t *plen,
|
uint8_t bt_addr[6], uint32_t *result)
|
{
|
|
struct rtk_bt_vendor_config *config =
|
(struct rtk_bt_vendor_config *)config_buf;
|
RT_U16 config_len = 0, temp = 0;
|
struct rtk_bt_vendor_config_entry *entry = NULL;
|
RT_U16 i;
|
RT_U32 baudrate = 0;
|
uint32_t flags = 0;
|
uint32_t add_flags;
|
#ifdef USE_CUSTOMER_ADDRESS
|
uint8_t j = 0;
|
struct patch_info *pent = rtk_hw_cfg.patch_ent;
|
#endif
|
#ifdef EXTRA_CONFIG_OPTION
|
uint8_t *head = config_buf;
|
|
add_flags = config_flags;
|
#endif
|
|
if (!config || !plen)
|
return NULL;
|
|
RS_INFO("Original Cfg len %u", *plen);
|
config_len = le16_to_cpu(config->data_len);
|
entry = config->entry;
|
|
if (le32_to_cpu(config->signature) != RTK_VENDOR_CONFIG_MAGIC) {
|
RS_ERR("signature magic number(%x) is incorrect",
|
(unsigned int)config->signature);
|
return NULL;
|
}
|
|
if (config_len != *plen - sizeof(struct rtk_bt_vendor_config)) {
|
RS_ERR("config len(%d) is incorrect(%zd)", config_len,
|
*plen - sizeof(struct rtk_bt_vendor_config));
|
return NULL;
|
}
|
|
for (i = 0; i < config_len;) {
|
|
switch (le16_to_cpu(entry->offset)) {
|
#ifdef USE_CUSTOMER_ADDRESS
|
case 0x003c:
|
case 0x0044:
|
if (!customer_bdaddr)
|
break;
|
if (pent->chip_type > CHIP_BEFORE &&
|
le16_to_cpu(entry->offset) != 0x44)
|
break;
|
if (pent->chip_type <= CHIP_BEFORE &&
|
le16_to_cpu(entry->offset) != 0x3c)
|
break;
|
for (j = 0; j < entry->entry_len; j++)
|
entry->entry_data[j] = bt_addr[j];
|
flags |= CONFIG_BTMAC;
|
RS_INFO("BT MAC found %02x:%02x:%02x:%02x:%02x:%02x",
|
entry->entry_data[5],
|
entry->entry_data[4],
|
entry->entry_data[3],
|
entry->entry_data[2],
|
entry->entry_data[1],
|
entry->entry_data[0]);
|
break;
|
#endif
|
case 0xc:
|
{
|
#ifdef BAUDRATE_4BYTES
|
baudrate =
|
get_unaligned_le32(entry->entry_data);
|
#else
|
baudrate =
|
get_unaligned_le16(entry->entry_data);
|
#endif
|
|
if (entry->entry_len >= 12) //0ffset 0x18 - 0xc
|
{
|
rtk_hw_cfg.hw_flow_control = (entry->entry_data[12] & 0x4) ? 1 : 0; //0x18 byte bit2
|
//rtk_hw_cfg.hw_flow_control = 0;
|
}
|
RS_DBG
|
("config baud rate to :0x%08x, hwflowcontrol:%x, %x",
|
(unsigned int)baudrate,
|
entry->entry_data[12],
|
rtk_hw_cfg.hw_flow_control);
|
break;
|
}
|
#ifdef EXTRA_CONFIG_OPTION
|
case 0x015b:
|
if (!(add_flags & CONFIG_TXPOWER))
|
break;
|
add_flags &= ~CONFIG_TXPOWER;
|
if (txpower_len != entry->entry_len) {
|
RS_ERR("invalid tx power cfg len %u, %u",
|
txpower_len, entry->entry_len);
|
break;
|
}
|
memcpy(entry->entry_data, txpower_cfg, txpower_len);
|
RS_INFO("Replace Tx power Cfg %02x %02x %02x %02x",
|
txpower_cfg[0], txpower_cfg[1], txpower_cfg[2],
|
txpower_cfg[3]);
|
break;
|
case 0x01e6:
|
if (!(add_flags & CONFIG_XTAL))
|
break;
|
add_flags &= ~CONFIG_XTAL;
|
RS_INFO("Replace XTAL Cfg 0x%02x", xtal_cfg);
|
entry->entry_data[0] = xtal_cfg;
|
break;
|
#endif
|
default:
|
RS_DBG("cfg offset (%04x),length (%02x)", entry->offset,
|
entry->entry_len);
|
break;
|
}
|
temp = entry->entry_len +
|
sizeof(struct rtk_bt_vendor_config_entry);
|
i += temp;
|
entry = (struct rtk_bt_vendor_config_entry *)((RT_U8 *)entry +
|
temp);
|
}
|
|
#ifdef USE_CUSTOMER_ADDRESS
|
if (!(flags & CONFIG_BTMAC) && customer_bdaddr) {
|
uint8_t *b;
|
|
b = config_buf + *plen;
|
if (pent->chip_type > CHIP_BEFORE) {
|
b[0] = 0x44;
|
b[1] = 0x00;
|
} else {
|
b[0] = 0x3c;
|
b[1] = 0x00;
|
}
|
RS_INFO("add bdaddr sec, offset %02x%02x", b[1], b[0]);
|
b[2] = 6;
|
for (j = 0; j < 6; j++)
|
b[3 + j] = bt_addr[j];
|
|
*plen += 9;
|
config_len += 9;
|
temp = *plen - 6;
|
|
config_buf[4] = (temp & 0xff);
|
config_buf[5] = ((temp >> 8) & 0xff);
|
}
|
#endif
|
|
#ifdef EXTRA_CONFIG_OPTION
|
temp = *plen;
|
if (add_flags & CONFIG_TXPOWER)
|
temp += (2 + 1 + txpower_len);
|
if ((add_flags & CONFIG_XTAL))
|
temp += (2 + 1 + 1);
|
|
if (add_flags) {
|
RS_INFO("Add extra configs");
|
config_buf = head;
|
temp -= sizeof(struct rtk_bt_vendor_config);
|
config_buf[4] = (temp & 0xff);
|
config_buf[5] = ((temp >> 8) & 0xff);
|
config_buf += *plen;
|
if (add_flags & CONFIG_TXPOWER) {
|
RS_INFO("Add Tx power Cfg");
|
*config_buf++ = 0x5b;
|
*config_buf++ = 0x01;
|
*config_buf++ = txpower_len;
|
memcpy(config_buf, txpower_cfg, txpower_len);
|
config_buf += txpower_len;
|
}
|
if ((add_flags & CONFIG_XTAL)) {
|
RS_INFO("Add XTAL Cfg");
|
*config_buf++ = 0xe6;
|
*config_buf++ = 0x01;
|
*config_buf++ = 1;
|
*config_buf++ = xtal_cfg;
|
}
|
*plen = config_buf - head;
|
config_buf = head;
|
}
|
#endif
|
|
done:
|
*result = baudrate;
|
return config_buf;
|
}
|
|
#ifdef USE_CUSTOMER_ADDRESS
|
int bachk(const char *str)
|
{
|
if (!str)
|
return -1;
|
|
if (strlen(str) != 17)
|
return -1;
|
|
while (*str) {
|
if (!isxdigit(*str++))
|
return -1;
|
|
if (!isxdigit(*str++))
|
return -1;
|
|
if (*str == 0)
|
break;
|
|
if (*str++ != ':')
|
return -1;
|
}
|
|
return 0;
|
}
|
/**
|
* get random realtek Bluetooth addr.
|
*
|
* @param bt_addr where bt addr is stored
|
*
|
*/
|
/* static void rtk_get_ram_addr(char bt_addr[0])
|
* {
|
* srand(time(NULL) + getpid() + getpid() * 987654 + rand());
|
*
|
* RT_U32 addr = rand();
|
* memcpy(bt_addr, &addr, sizeof(RT_U8));
|
* }
|
*/
|
|
/**
|
* Write the random bt addr to the file /data/misc/bluetoothd/bt_mac/btmac.txt.
|
*
|
* @param bt_addr where bt addr is stored
|
*
|
*/
|
/* static void rtk_write_btmac2file(char bt_addr[6])
|
* {
|
* int fd;
|
* fd = open(BT_ADDR_FILE, O_CREAT | O_RDWR | O_TRUNC);
|
*
|
* if (fd > 0) {
|
* chmod(BT_ADDR_FILE, 0666);
|
* char addr[18] = { 0 };
|
* addr[17] = '\0';
|
* sprintf(addr, "%2x:%2x:%2x:%2x:%2x:%2x", bt_addr[0], bt_addr[1],
|
* bt_addr[2], bt_addr[3], bt_addr[4], bt_addr[5]);
|
* write(fd, addr, strlen(addr));
|
* close(fd);
|
* } else {
|
* RS_ERR("open file error:%s\n", BT_ADDR_FILE);
|
* }
|
* }
|
*/
|
#endif
|
|
/**
|
* Get realtek Bluetooth config file. The bt addr arg is stored in /data/btmac.txt, if there is not this file,
|
* change to /data/misc/bluetoothd/bt_mac/btmac.txt. If both of them are not found, using
|
* random bt addr.
|
*
|
* @param config_buf point to the content of realtek Bluetooth config file
|
* @param config_baud_rate the baudrate set in the config file
|
* @return file_len the length of config file
|
*/
|
int rtk_get_bt_config(struct btrtl_info *btrtl, uint8_t **config_buf,
|
RT_U32 *config_baud_rate)
|
{
|
char bt_config_file_name[PATH_MAX] = { 0 };
|
RT_U8 *bt_addr_temp = NULL;
|
uint8_t bt_addr[6] = { 0x00, 0xe0, 0x4c, 0x88, 0x88, 0x88 };
|
struct stat st;
|
size_t filelen;
|
size_t tlength;
|
int fd;
|
int ret = 0;
|
int i = 0;
|
|
#ifdef USE_CUSTOMER_ADDRESS
|
#define BDADDR_STRING_LEN 17
|
size_t size;
|
size_t result;
|
uint8_t tbuf[BDADDR_STRING_LEN + 1];
|
char *str;
|
|
if (stat(BT_ADDR_FILE, &st) < 0) {
|
RS_INFO("Couldnt access customer BT MAC file %s",
|
BT_ADDR_FILE);
|
|
/* for (i = 0; i < 6; i++)
|
* rtk_get_ram_addr(&bt_addr[i]);
|
* rtk_write_btmac2file(bt_addr);
|
*/
|
goto GET_CONFIG;
|
}
|
|
size = st.st_size;
|
/* Only read the first 17-byte if the file length is larger */
|
if (size > BDADDR_STRING_LEN)
|
size = BDADDR_STRING_LEN;
|
|
fd = open(BT_ADDR_FILE, O_RDONLY);
|
if (fd == -1) {
|
RS_ERR("Couldnt open BT MAC file %s, %s", BT_ADDR_FILE,
|
strerror(errno));
|
} else {
|
memset(tbuf, 0, sizeof(tbuf));
|
result = read(fd, tbuf, size);
|
close(fd);
|
if (result == -1) {
|
RS_ERR("Couldnt read BT MAC file %s, err %s",
|
BT_ADDR_FILE, strerror(errno));
|
goto GET_CONFIG;
|
}
|
|
if (bachk(tbuf) < 0) {
|
goto GET_CONFIG;
|
}
|
|
str = tbuf;
|
for (i = 5; i >= 0; i--) {
|
bt_addr[i] = (uint8_t)strtoul(str, NULL, 16);
|
str += 3;
|
}
|
|
/*reserve LAP addr from 0x9e8b00 to 0x9e8b3f, change to 0x008b** */
|
if (0x9e == bt_addr[3] && 0x8b == bt_addr[4]
|
&& (bt_addr[5] <= 0x3f)) {
|
bt_addr[3] = 0x00;
|
}
|
|
RS_DBG("BT MAC is %02x:%02x:%02x:%02x:%02x:%02x",
|
bt_addr[5], bt_addr[4],
|
bt_addr[3], bt_addr[2],
|
bt_addr[1], bt_addr[0]);
|
customer_bdaddr = 1;
|
}
|
#endif
|
|
GET_CONFIG:
|
//ret = sprintf(bt_config_file_name, BT_CONFIG_DIRECTORY "rtlbt_config");
|
ret = sprintf(bt_config_file_name, "%s", BT_CONFIG_DIRECTORY);
|
strcat(bt_config_file_name, btrtl->patch_ent->config_file);
|
if (stat(bt_config_file_name, &st) < 0) {
|
RS_ERR("can't access bt config file:%s, errno:%d\n",
|
bt_config_file_name, errno);
|
return -1;
|
}
|
|
filelen = st.st_size;
|
|
if ((fd = open(bt_config_file_name, O_RDONLY)) < 0) {
|
perror("Can't open bt config file");
|
return -1;
|
}
|
|
tlength = filelen;
|
#ifdef USE_CUSTOMER_ADDRESS
|
tlength += 9;
|
#endif
|
|
#ifdef EXTRA_CONFIG_OPTION
|
config_file_proc(EXTRA_CONFIG_FILE);
|
if (!xtalset_supported())
|
config_flags &= ~CONFIG_XTAL;
|
if (config_flags & CONFIG_TXPOWER)
|
tlength += 7;
|
if (config_flags & CONFIG_XTAL)
|
tlength += 4;
|
#endif
|
|
if ((*config_buf = malloc(tlength)) == NULL) {
|
RS_DBG("Couldnt malloc buffer for config (%zd)\n", tlength);
|
close(fd);
|
return -1;
|
}
|
//we may need to parse this config file.
|
//for easy debug, only get need data.
|
|
if (read(fd, *config_buf, filelen) < (ssize_t) filelen) {
|
perror("Can't load bt config file");
|
free(*config_buf);
|
close(fd);
|
return -1;
|
}
|
|
*config_buf = rtk_parse_config_file(*config_buf, &filelen, bt_addr,
|
config_baud_rate);
|
util_hexdump((const uint8_t *)*config_buf, filelen);
|
RS_INFO("Cfg length %u", filelen);
|
RS_DBG("Get config baud rate from config file:%x",
|
(unsigned int)*config_baud_rate);
|
|
close(fd);
|
return filelen;
|
}
|
|
/**
|
* Realtek change speed with h5 proto. Using vendor specified command packet to achieve this.
|
*
|
* @warning it will waiting 2s for reply.
|
*
|
* @param fd uart file descriptor
|
* @param baudrate the speed want to change
|
*
|
*/
|
int rtk_vendor_change_speed_h5(int fd, RT_U32 baudrate)
|
{
|
struct sk_buff *cmd_change_bdrate = NULL;
|
unsigned char cmd[7] = { 0 };
|
int retlen;
|
unsigned char bytes[READ_DATA_SIZE];
|
struct sigaction sa;
|
|
sa.sa_handler = h5_tinit_sig_alarm;
|
sigaction(SIGALRM, &sa, NULL);
|
|
cmd[0] = 0x17; //ocf
|
cmd[1] = 0xfc; //ogf, vendor specified
|
|
cmd[2] = 4; //length;
|
|
baudrate = cpu_to_le32(baudrate);
|
#ifdef BAUDRATE_4BYTES
|
memcpy((RT_U16 *) & cmd[3], &baudrate, 4);
|
#else
|
memcpy((RT_U16 *) & cmd[3], &baudrate, 2);
|
|
cmd[5] = 0;
|
cmd[6] = 0;
|
#endif
|
|
RS_DBG("baudrate in change speed command: 0x%x 0x%x 0x%x 0x%x \n",
|
cmd[3], cmd[4], cmd[5], cmd[6]);
|
|
cmd_change_bdrate =
|
h5_prepare_pkt(&rtk_hw_cfg, cmd, 7, HCI_COMMAND_PKT);
|
if (!cmd_change_bdrate) {
|
RS_ERR("Prepare command packet for change speed fail");
|
return -1;
|
}
|
|
rtk_hw_cfg.host_last_cmd = cmd_change_bdrate;
|
alarm(1);
|
write(fd, cmd_change_bdrate->data, cmd_change_bdrate->data_len);
|
|
while (rtk_hw_cfg.link_estab_state == H5_INIT) {
|
if ((retlen = read_check_rtk(fd, &bytes, READ_DATA_SIZE)) == -1) {
|
perror("read fail");
|
return -1;
|
}
|
//add pure ack check
|
h5_recv(&rtk_hw_cfg, &bytes, retlen);
|
}
|
|
alarm(0);
|
return 0;
|
}
|
|
/**
|
* Init realtek Bluetooth h5 proto. h5 proto is added by realtek in the right kernel.
|
* Generally there are two steps: h5 sync and h5 config
|
*
|
* @param fd uart file descriptor
|
* @param ti termios struct
|
*
|
*/
|
int rtk_init_h5(int fd, struct termios *ti)
|
{
|
unsigned char bytes[READ_DATA_SIZE];
|
struct sigaction sa;
|
int retlen;
|
struct itimerval value;
|
|
/* set even parity */
|
ti->c_cflag |= PARENB;
|
ti->c_cflag &= ~(PARODD);
|
if (tcsetattr(fd, TCSANOW, ti) < 0) {
|
RS_ERR("Can't set port settings");
|
return -1;
|
}
|
|
rtk_hw_cfg.h5_max_retries = H5_MAX_RETRY_COUNT;
|
|
alarm(0);
|
memset(&sa, 0, sizeof(sa));
|
sa.sa_flags = SA_NOCLDSTOP;
|
sa.sa_handler = h5_tsync_sig_alarm;
|
sigaction(SIGALRM, &sa, NULL);
|
|
/* h5 sync */
|
h5_tsync_sig_alarm(0);
|
rtk_hw_cfg.link_estab_state = H5_SYNC;
|
while (rtk_hw_cfg.link_estab_state == H5_SYNC) {
|
if ((retlen = read_check_rtk(fd, &bytes, READ_DATA_SIZE)) == -1) {
|
RS_ERR("H5 Read Sync Response Failed");
|
|
value.it_value.tv_sec = 0;
|
value.it_value.tv_usec = 0;
|
value.it_interval.tv_sec = 0;
|
value.it_interval.tv_usec = 0;
|
setitimer(ITIMER_REAL, &value, NULL);
|
|
return -1;
|
}
|
h5_recv(&rtk_hw_cfg, &bytes, retlen);
|
}
|
|
value.it_value.tv_sec = 0;
|
value.it_value.tv_usec = 0;
|
value.it_interval.tv_sec = 0;
|
value.it_interval.tv_usec = 0;
|
setitimer(ITIMER_REAL, &value, NULL);
|
|
/* h5 config */
|
sa.sa_handler = h5_tconf_sig_alarm;
|
sigaction(SIGALRM, &sa, NULL);
|
h5_tconf_sig_alarm(0);
|
while (rtk_hw_cfg.link_estab_state == H5_CONFIG) {
|
if ((retlen = read_check_rtk(fd, &bytes, READ_DATA_SIZE)) == -1) {
|
RS_ERR("H5 Read Config Response Failed");
|
value.it_value.tv_sec = 0;
|
value.it_value.tv_usec = 0;
|
value.it_interval.tv_sec = 0;
|
value.it_interval.tv_usec = 0;
|
setitimer(ITIMER_REAL, &value, NULL);
|
return -1;
|
}
|
h5_recv(&rtk_hw_cfg, &bytes, retlen);
|
}
|
|
value.it_value.tv_sec = 0;
|
value.it_value.tv_usec = 0;
|
value.it_interval.tv_sec = 0;
|
value.it_interval.tv_usec = 0;
|
setitimer(ITIMER_REAL, &value, NULL);
|
|
rtk_send_pure_ack_down(fd);
|
RS_DBG("H5 init finished\n");
|
|
rtk_hw_cfg.rom_version_cmd_state = cmd_not_send;
|
rtk_hw_cfg.hci_version_cmd_state = cmd_not_send;
|
return 0;
|
}
|
|
/**
|
* Download realtek firmware and config file from uart with the proto.
|
* Parse the content to serval packets follow the proto and then write the packets from uart
|
*
|
* @param fd uart file descriptor
|
* @param buf addr where stor the content of firmware and config file
|
* @param filesize length of buf
|
* @param is_sent_changerate if baudrate need to be changed
|
* @param proto realtek Bluetooth protocol, shall be either HCI_UART_H4 or HCI_UART_3WIRE
|
*
|
*/
|
static int rtk_download_fw_config(int fd, RT_U8 * buf, size_t filesize,
|
int is_sent_changerate, int proto,
|
struct termios *ti)
|
{
|
uint8_t iCurIndex = 0;
|
uint8_t iCurLen = 0;
|
uint8_t iEndIndex = 0;
|
uint8_t iLastPacketLen = 0;
|
uint8_t iAdditionPkt = 0;
|
uint8_t iTotalIndex = 0;
|
uint8_t iCmdSentNum = 0;
|
unsigned char *bufpatch;
|
uint8_t i, j;
|
|
iEndIndex = (uint8_t) ((filesize - 1) / PATCH_DATA_FIELD_MAX_SIZE);
|
iLastPacketLen = (filesize) % PATCH_DATA_FIELD_MAX_SIZE;
|
|
/* if (is_sent_changerate)
|
* iCmdSentNum++;
|
* if (rtk_hw_cfg.rom_version_cmd_state >= cmd_has_sent)
|
* iCmdSentNum++;
|
* if (rtk_hw_cfg.hci_version_cmd_state >= cmd_has_sent)
|
* iCmdSentNum++; */
|
iCmdSentNum += rtk_hw_cfg.num_of_cmd_sent;
|
|
iAdditionPkt =
|
(iEndIndex + 1 + iCmdSentNum) % 8 ? (8 -
|
(iEndIndex + 1 +
|
iCmdSentNum) % 8) : 0;
|
iTotalIndex = iAdditionPkt + iEndIndex;
|
rtk_hw_cfg.total_num = iTotalIndex; //init TotalIndex
|
|
RS_DBG("iEndIndex:%d iLastPacketLen:%d iAdditionpkt:%d\n", iEndIndex,
|
iLastPacketLen, iAdditionPkt);
|
|
if (iLastPacketLen == 0)
|
iLastPacketLen = PATCH_DATA_FIELD_MAX_SIZE;
|
|
bufpatch = buf;
|
|
for (i = 0; i <= iTotalIndex; i++) {
|
/* Index will roll over when it reaches 0x80. */
|
if (i > 0x7f)
|
j = (i & 0x7f) + 1;
|
else
|
j = i;
|
|
if (i < iEndIndex) {
|
iCurIndex = j;
|
iCurLen = PATCH_DATA_FIELD_MAX_SIZE;
|
} else if (i == iEndIndex) {
|
/* Send last data packets */
|
if (i == iTotalIndex)
|
iCurIndex = j | 0x80;
|
else
|
iCurIndex = j;
|
iCurLen = iLastPacketLen;
|
} else if (i < iTotalIndex) {
|
/* Send additional packets */
|
iCurIndex = j;
|
bufpatch = NULL;
|
iCurLen = 0;
|
RS_DBG("Send additional packet %u", iCurIndex);
|
} else {
|
/* Send end packet */
|
iCurIndex = j | 0x80;
|
bufpatch = NULL;
|
iCurLen = 0;
|
RS_DBG("Send end packet %u", iCurIndex);
|
}
|
|
if (iCurIndex & 0x80)
|
RS_DBG("Send FW last command");
|
|
if (proto == HCI_UART_H4) {
|
iCurIndex = hci_download_patch_h4(fd, iCurIndex,
|
bufpatch, iCurLen);
|
if ((iCurIndex != j) && (i != rtk_hw_cfg.total_num)) {
|
RS_DBG(
|
"index mismatch j %d, iCurIndex:%d, fail\n",
|
j, iCurIndex);
|
return -1;
|
}
|
} else if (proto == HCI_UART_3WIRE) {
|
if (hci_download_patch(fd, iCurIndex, bufpatch, iCurLen,
|
ti) < 0)
|
return -1;
|
}
|
|
if (iCurIndex < iEndIndex) {
|
bufpatch += PATCH_DATA_FIELD_MAX_SIZE;
|
}
|
}
|
|
/* Set last ack packet down */
|
if (proto == HCI_UART_3WIRE) {
|
rtk_send_pure_ack_down(fd);
|
}
|
|
return 0;
|
}
|
|
/**
|
* Get realtek Bluetooth firmaware file. The content will be saved in *fw_buf which is malloc here.
|
* The length malloc here will be lager than length of firmware file if there is a config file.
|
* The content of config file will copy to the tail of *fw_buf in rtk_config.
|
*
|
* @param fw_buf point to the addr where stored the content of firmware.
|
* @param addi_len length of config file.
|
* @return length of *fw_buf.
|
*
|
*/
|
int rtk_get_bt_firmware(struct btrtl_info *btrtl, RT_U8 **fw_buf)
|
{
|
const char *filename;
|
struct stat st;
|
int fd = -1;
|
size_t fwsize;
|
static char firmware_file_name[PATH_MAX] = { 0 };
|
int ret = 0;
|
|
sprintf(firmware_file_name, "%s", FIRMWARE_DIRECTORY);
|
strcat(firmware_file_name, btrtl->patch_ent->patch_file);
|
filename = firmware_file_name;
|
|
if (stat(filename, &st) < 0) {
|
RS_ERR("Can't access firmware %s, %s", filename,
|
strerror(errno));
|
return -1;
|
}
|
|
fwsize = st.st_size;
|
|
if ((fd = open(filename, O_RDONLY)) < 0) {
|
RS_ERR("Can't open firmware, errno:%d", errno);
|
return -1;
|
}
|
|
if (!(*fw_buf = malloc(fwsize))) {
|
RS_ERR("Can't alloc memory for fw&config, errno:%d", errno);
|
close(fd);
|
return -1;
|
}
|
|
if (read(fd, *fw_buf, fwsize) < (ssize_t) fwsize) {
|
free(*fw_buf);
|
*fw_buf = NULL;
|
close(fd);
|
return -1;
|
}
|
RS_DBG("Load FW OK");
|
close(fd);
|
return fwsize;
|
}
|
|
//These two function(rtk<-->uart speed transfer) need check Host uart speed at first!!!! IMPORTANT
|
//add more speed if neccessary
|
typedef struct _baudrate_ex {
|
RT_U32 rtk_speed;
|
int uart_speed;
|
} baudrate_ex;
|
|
#ifdef BAUDRATE_4BYTES
|
baudrate_ex baudrates[] = {
|
#ifdef RTL_8703A_SUPPORT
|
{0x00004003, 1500000}, /* for rtl8703as */
|
#endif
|
{0x0252C014, 115200},
|
{0x0252C00A, 230400},
|
{0x05F75004, 921600},
|
{0x00005004, 1000000},
|
{0x04928002, 1500000},
|
{0x01128002, 1500000}, //8761AT
|
{0x00005002, 2000000},
|
{0x0000B001, 2500000},
|
{0x04928001, 3000000},
|
{0x052A6001, 3500000},
|
{0x00005001, 4000000},
|
};
|
#else
|
baudrate_ex baudrates[] = {
|
{0x701d, 115200}
|
{0x6004, 921600},
|
{0x4003, 1500000},
|
{0x5002, 2000000},
|
{0x8001, 3000000},
|
{0x9001, 3000000},
|
{0x7001, 3500000},
|
{0x5001, 4000000},
|
};
|
#endif
|
|
/**
|
* Change realtek Bluetooth speed to uart speed. It is matching in the struct baudrates:
|
*
|
* @code
|
* baudrate_ex baudrates[] =
|
* {
|
* {0x7001, 3500000},
|
* {0x6004, 921600},
|
* {0x4003, 1500000},
|
* {0x5001, 4000000},
|
* {0x5002, 2000000},
|
* {0x8001, 3000000},
|
* {0x701d, 115200}
|
* };
|
* @endcode
|
*
|
* If there is no match in baudrates, uart speed will be set as #115200.
|
*
|
* @param rtk_speed realtek Bluetooth speed
|
* @param uart_speed uart speed
|
*
|
*/
|
static void rtk_speed_to_uart_speed(RT_U32 rtk_speed, RT_U32 * uart_speed)
|
{
|
*uart_speed = 115200;
|
|
unsigned int i;
|
for (i = 0; i < sizeof(baudrates) / sizeof(baudrate_ex); i++) {
|
if (baudrates[i].rtk_speed == rtk_speed) {
|
*uart_speed = baudrates[i].uart_speed;
|
return;
|
}
|
}
|
return;
|
}
|
|
/**
|
* Change uart speed to realtek Bluetooth speed. It is matching in the struct baudrates:
|
*
|
* @code
|
* baudrate_ex baudrates[] =
|
* {
|
* {0x7001, 3500000},
|
* {0x6004, 921600},
|
* {0x4003, 1500000},
|
* {0x5001, 4000000},
|
* {0x5002, 2000000},
|
* {0x8001, 3000000},
|
* {0x701d, 115200}
|
* };
|
* @endcode
|
*
|
* If there is no match in baudrates, realtek Bluetooth speed will be set as #0x701D.
|
*
|
* @param uart_speed uart speed
|
* @param rtk_speed realtek Bluetooth speed
|
*
|
*/
|
static inline void uart_speed_to_rtk_speed(int uart_speed, RT_U32 * rtk_speed)
|
{
|
*rtk_speed = 0x701D;
|
|
unsigned int i;
|
for (i = 0; i < sizeof(baudrates) / sizeof(baudrate_ex); i++) {
|
if (baudrates[i].uart_speed == uart_speed) {
|
*rtk_speed = baudrates[i].rtk_speed;
|
return;
|
}
|
}
|
|
return;
|
}
|
|
static void rtk_get_chip_type_timeout(int sig)
|
{
|
static int retries = 0;
|
int len = 0;
|
struct btrtl_info *btrtl = &rtk_hw_cfg;
|
|
RS_INFO("RTK get HCI_VENDOR_READ_RTK_CHIP_TYPE_Command\n");
|
if (retries < btrtl->h5_max_retries) {
|
RS_DBG("rtk get chip type timerout, retry:%d\n", retries);
|
if (btrtl->host_last_cmd) {
|
len = write(btrtl->serial_fd,
|
btrtl->host_last_cmd->data,
|
btrtl->host_last_cmd->data_len);
|
}
|
retries++;
|
alarm(3);
|
return;
|
}
|
tcflush(btrtl->serial_fd, TCIOFLUSH);
|
RS_ERR("rtk get chip type cmd complete event timed out\n");
|
exit(1);
|
}
|
|
void rtk_get_chip_type(int dd)
|
{
|
unsigned char bytes[READ_DATA_SIZE];
|
int retlen;
|
struct sigaction sa;
|
/* 0xB000A094 */
|
unsigned char cmd_buff[] = {0x61, 0xfc,
|
0x05, 0x00, 0x94, 0xa0, 0x00, 0xb0};
|
struct sk_buff *nskb;
|
struct btrtl_info *btrtl = &rtk_hw_cfg;
|
|
nskb = h5_prepare_pkt(btrtl, cmd_buff, sizeof(cmd_buff),
|
HCI_COMMAND_PKT);
|
if (btrtl->host_last_cmd){
|
skb_free(btrtl->host_last_cmd);
|
btrtl->host_last_cmd = NULL;
|
}
|
|
btrtl->host_last_cmd = nskb;
|
|
write(dd, nskb->data, nskb->data_len);
|
RS_INFO("RTK send HCI_VENDOR_READ_CHIP_TYPE_Command\n");
|
|
alarm(0);
|
memset(&sa, 0, sizeof(sa));
|
sa.sa_flags = SA_NOCLDSTOP;
|
sa.sa_handler = rtk_get_chip_type_timeout;
|
sigaction(SIGALRM, &sa, NULL);
|
|
alarm(3);
|
while (btrtl->chip_type_cmd_state != event_received) {
|
if ((retlen = read_check_rtk(dd, &bytes, READ_DATA_SIZE)) == -1) {
|
perror("rtk get chip type: read fail");
|
return;
|
}
|
h5_recv(btrtl, &bytes, retlen);
|
}
|
alarm(0);
|
return;
|
}
|
|
static void rtk_get_eversion_timeout(int sig)
|
{
|
static int retries = 0;
|
int len = 0;
|
|
RS_DBG("RTK get HCI_VENDOR_READ_RTK_ROM_VERISION_Command\n");
|
if (retries < rtk_hw_cfg.h5_max_retries) {
|
RS_DBG("rtk get eversion timerout, retry:%d\n", retries);
|
if (rtk_hw_cfg.host_last_cmd) {
|
len = write(rtk_hw_cfg.serial_fd,
|
rtk_hw_cfg.host_last_cmd->data,
|
rtk_hw_cfg.host_last_cmd->data_len);
|
}
|
retries++;
|
alarm(3);
|
return;
|
}
|
tcflush(rtk_hw_cfg.serial_fd, TCIOFLUSH);
|
RS_ERR("rtk get eversion cmd complete event timed out\n");
|
exit(1);
|
}
|
|
/**
|
* Send vendor cmd to get eversion: 0xfc6d
|
* If Rom code does not support this cmd, use default.
|
*/
|
void rtk_get_eversion(int dd)
|
{
|
unsigned char bytes[READ_DATA_SIZE];
|
int retlen;
|
struct sigaction sa;
|
unsigned char read_rom_patch_cmd[3] = { 0x6d, 0xfc, 00 };
|
struct sk_buff *nskb =
|
h5_prepare_pkt(&rtk_hw_cfg, read_rom_patch_cmd, 3, HCI_COMMAND_PKT);
|
|
if (rtk_hw_cfg.host_last_cmd) {
|
skb_free(rtk_hw_cfg.host_last_cmd);
|
rtk_hw_cfg.host_last_cmd = NULL;
|
}
|
|
rtk_hw_cfg.host_last_cmd = nskb;
|
|
write(dd, nskb->data, nskb->data_len);
|
rtk_hw_cfg.rom_version_cmd_state = cmd_has_sent;
|
RS_DBG("RTK send HCI_VENDOR_READ_RTK_ROM_VERISION_Command\n");
|
|
alarm(0);
|
memset(&sa, 0, sizeof(sa));
|
sa.sa_flags = SA_NOCLDSTOP;
|
sa.sa_handler = rtk_get_eversion_timeout;
|
sigaction(SIGALRM, &sa, NULL);
|
|
alarm(3);
|
while (rtk_hw_cfg.rom_version_cmd_state != event_received) {
|
if ((retlen = read_check_rtk(dd, &bytes, READ_DATA_SIZE)) == -1) {
|
perror("rtk get eversion: read fail");
|
return;
|
}
|
h5_recv(&rtk_hw_cfg, &bytes, retlen);
|
}
|
alarm(0);
|
return;
|
}
|
|
static void rtk_get_lmp_version_timeout(int sig)
|
{
|
static int retries = 0;
|
RS_DBG("RTK get HCI_VENDOR_READ_RTK_LMP_VERISION_Command\n");
|
if (retries < rtk_hw_cfg.h5_max_retries) {
|
RS_DBG("rtk get lmp version timeout, retry: %d\n", retries);
|
if (rtk_hw_cfg.host_last_cmd) {
|
int len = write(rtk_hw_cfg.serial_fd,
|
rtk_hw_cfg.host_last_cmd->data,
|
rtk_hw_cfg.host_last_cmd->data_len);
|
}
|
retries++;
|
alarm(3);
|
return;
|
}
|
tcflush(rtk_hw_cfg.serial_fd, TCIOFLUSH);
|
RS_ERR("rtk get lmp version cmd complete event timed out\n");
|
exit(1);
|
}
|
|
void rtk_get_lmp_version(int dd)
|
{
|
unsigned char bytes[READ_DATA_SIZE];
|
int retlen;
|
struct sigaction sa;
|
unsigned char read_rom_patch_cmd[3] = { 0x01, 0x10, 00 };
|
struct sk_buff *nskb = h5_prepare_pkt(&rtk_hw_cfg, read_rom_patch_cmd, 3, HCI_COMMAND_PKT); //data:len+head:4
|
|
if (rtk_hw_cfg.host_last_cmd) {
|
skb_free(rtk_hw_cfg.host_last_cmd);
|
rtk_hw_cfg.host_last_cmd = NULL;
|
}
|
|
rtk_hw_cfg.host_last_cmd = nskb;
|
|
write(dd, nskb->data, nskb->data_len);
|
rtk_hw_cfg.hci_version_cmd_state = cmd_has_sent;
|
RS_DBG("RTK send HCI_VENDOR_READ_RTK_ROM_VERISION_Command\n");
|
|
alarm(0);
|
memset(&sa, 0, sizeof(sa));
|
sa.sa_flags = SA_NOCLDSTOP;
|
sa.sa_handler = rtk_get_lmp_version_timeout;
|
sigaction(SIGALRM, &sa, NULL);
|
|
alarm(3);
|
while (rtk_hw_cfg.hci_version_cmd_state != event_received) {
|
if ((retlen = read_check_rtk(dd, &bytes, READ_DATA_SIZE)) == -1) {
|
perror("read fail");
|
return;
|
}
|
h5_recv(&rtk_hw_cfg, &bytes, retlen);
|
}
|
alarm(0);
|
return;
|
}
|
|
static int rtk_max_retries = 5;
|
|
static void rtk_local_ver_sig_alarm(int sig)
|
{
|
uint8_t cmd[4] = { 0x01, 0x01, 0x10, 0x00 };
|
static int retries;
|
|
if (retries < rtk_max_retries) {
|
retries++;
|
if (write(rtk_hw_cfg.serial_fd, cmd, sizeof(cmd)) < 0)
|
return;
|
alarm(1);
|
return;
|
}
|
|
tcflush(rtk_hw_cfg.serial_fd, TCIOFLUSH);
|
RS_ERR("init timed out, read local ver fails");
|
exit(1);
|
}
|
|
static void rtk_hci_local_ver(int fd)
|
{
|
struct sigaction sa;
|
uint8_t result[258];
|
int ret;
|
|
alarm(0);
|
|
memset(&sa, 0, sizeof(sa));
|
sa.sa_flags = SA_NOCLDSTOP;
|
sa.sa_handler = rtk_local_ver_sig_alarm;
|
sigaction(SIGALRM, &sa, NULL);
|
|
rtk_local_ver_sig_alarm(0);
|
|
while (1) {
|
ret = read_hci_evt(fd, result, 0x0e);
|
/* break down is not needed if read fails, because the program
|
* will exit when alarm timeout
|
*/
|
if (ret < 0)
|
RS_ERR("%s: Read HCI event error.", __func__);
|
else
|
break;
|
}
|
|
/* Cancel pending alarm */
|
alarm(0);
|
|
if (ret != 15) {
|
RS_ERR("%s: incorrect complete event, len %u", __func__, ret);
|
exit(1);
|
}
|
|
if (result[6]) {
|
RS_ERR("%s: status is %u", __func__, result[6]);
|
exit(1);
|
}
|
|
rtk_hw_cfg.hci_rev = (uint32_t)result[9] << 8 | result[8];
|
rtk_hw_cfg.lmp_subver = (uint32_t)result[14] << 8 | result[13];
|
}
|
|
static void rtk_rom_ver_sig_alarm(int sig)
|
{
|
uint8_t cmd[4] = { 0x01, 0x6d, 0xfc, 0x00 };
|
static int retries;
|
|
if (retries < rtk_max_retries) {
|
retries++;
|
if (write(rtk_hw_cfg.serial_fd, cmd, sizeof(cmd)) < 0)
|
return;
|
alarm(1);
|
return;
|
}
|
|
tcflush(rtk_hw_cfg.serial_fd, TCIOFLUSH);
|
RS_ERR("init timed out, read rom ver fails");
|
exit(1);
|
}
|
|
static void rtk_hci_rom_ver(int fd)
|
{
|
struct sigaction sa;
|
uint8_t result[256];
|
int ret;
|
|
alarm(0);
|
|
memset(&sa, 0, sizeof(sa));
|
sa.sa_flags = SA_NOCLDSTOP;
|
sa.sa_handler = rtk_rom_ver_sig_alarm;
|
sigaction(SIGALRM, &sa, NULL);
|
|
rtk_rom_ver_sig_alarm(0);
|
|
while (1) {
|
ret = read_hci_evt(fd, result, 0x0e);
|
/* break down is not needed if read fails, because the program
|
* will exit when alarm timeout
|
*/
|
if (ret < 0)
|
RS_ERR("%s: Read HCI event error.", __func__);
|
else
|
break;
|
}
|
|
/* Cancel pending alarm */
|
alarm(0);
|
|
if (ret != 8) {
|
RS_ERR("%s: incorrect complete event, len %u", __func__, ret);
|
exit(1);
|
}
|
|
if (result[6]) {
|
RS_ERR("%s: status is %u", __func__, result[6]);
|
rtk_hw_cfg.eversion = 0;
|
} else
|
rtk_hw_cfg.eversion = result[7];
|
}
|
|
uint8_t rtk_get_fw_project_id(uint8_t * p_buf)
|
{
|
uint8_t opcode;
|
uint8_t len;
|
uint8_t data = 0;
|
|
do {
|
opcode = *p_buf;
|
len = *(p_buf - 1);
|
if (opcode == 0x00) {
|
if (len == 1) {
|
data = *(p_buf - 2);
|
RS_DBG
|
("rtk_get_fw_project_id: opcode %d, len %d, data %d",
|
opcode, len, data);
|
break;
|
} else {
|
RS_ERR("rtk_get_fw_project_id: invalid len %d",
|
len);
|
}
|
}
|
p_buf -= len + 2;
|
} while (*p_buf != 0xFF);
|
|
return data;
|
}
|
|
struct rtk_epatch_entry *rtk_get_patch_entry(void)
|
{
|
uint16_t i;
|
struct rtk_epatch *patch;
|
struct rtk_epatch_entry *entry;
|
uint8_t *p;
|
uint16_t chip_id;
|
uint32_t tmp;
|
|
patch = (struct rtk_epatch *)rtk_hw_cfg.fw_buf;
|
entry = (struct rtk_epatch_entry *)malloc(sizeof(*entry));
|
if (!entry) {
|
RS_ERR("failed to allocate mem for patch entry");
|
return NULL;
|
}
|
|
patch->number_of_patch = le16_to_cpu(patch->number_of_patch);
|
|
RS_DBG("fw_ver 0x%08x, patch_num %d",
|
le32_to_cpu(patch->fw_version), patch->number_of_patch);
|
|
for (i = 0; i < patch->number_of_patch; i++) {
|
RS_DBG("chip id 0x%04x",
|
get_unaligned_le16(rtk_hw_cfg.fw_buf + 14 + 2 * i));
|
if (get_unaligned_le16(rtk_hw_cfg.fw_buf + 14 + 2 * i) ==
|
rtk_hw_cfg.eversion + 1) {
|
entry->chipID = rtk_hw_cfg.eversion + 1;
|
entry->patch_length =
|
get_unaligned_le16(rtk_hw_cfg.fw_buf + 14 +
|
2 * patch->number_of_patch +
|
2 * i);
|
entry->start_offset =
|
get_unaligned_le32(rtk_hw_cfg.fw_buf + 14 +
|
4 * patch->number_of_patch +
|
4 * i);
|
RS_DBG("patch length is 0x%x", entry->patch_length);
|
RS_DBG("start offset is 0x%x", entry->start_offset);
|
|
entry->svn_ver = get_unaligned_le32(rtk_hw_cfg.fw_buf +
|
entry->start_offset +
|
entry->patch_length - 8);
|
entry->coex_ver = get_unaligned_le32(rtk_hw_cfg.fw_buf +
|
entry->start_offset +
|
entry->patch_length - 12);
|
|
RS_INFO("Svn version: %8d\n", entry->svn_ver);
|
tmp = ((entry->coex_ver >> 16) & 0x7ff) +
|
(entry->coex_ver >> 27) * 10000;
|
RS_INFO("Coexistence: BTCOEX_20%06d-%04x\n", tmp,
|
(entry->coex_ver & 0xffff));
|
|
break;
|
}
|
|
}
|
|
if (i == patch->number_of_patch) {
|
RS_ERR("failed to get entry");
|
free(entry);
|
entry = NULL;
|
}
|
|
return entry;
|
}
|
|
void rtk_get_final_patch(int fd, int proto)
|
{
|
struct btrtl_info *rtl = &rtk_hw_cfg;
|
uint8_t proj_id = 0;
|
struct rtk_epatch_entry *entry = NULL;
|
struct rtk_epatch *patch = (struct rtk_epatch *)rtl->fw_buf;
|
uint32_t svn_ver, coex_ver, tmp;
|
|
if ((proto == HCI_UART_H4) ||
|
((proto == HCI_UART_3WIRE) && (rtl->lmp_subver == ROM_LMP_8723a))) {
|
if (memcmp(rtl->fw_buf, RTK_EPATCH_SIGNATURE, 8) == 0) {
|
RS_ERR("Check signature error!");
|
rtl->dl_fw_flag = 0;
|
goto free_buf;
|
} else {
|
rtl->total_len = rtl->config_len + rtl->fw_len;
|
if (!(rtl->total_buf = malloc(rtl->total_len))) {
|
RS_ERR("Can't alloc mem for fw/config, errno:%d",
|
errno);
|
rtl->dl_fw_flag = 0;
|
rtl->total_len = 0;
|
goto free_buf;
|
} else {
|
RS_DBG("fw copy directy");
|
|
svn_ver = get_unaligned_le32(
|
rtl->fw_buf + rtl->fw_len - 8);
|
coex_ver = get_unaligned_le32(
|
rtl->fw_buf + rtl->fw_len - 12);
|
|
RS_INFO("Svn version: %8d\n", svn_ver);
|
tmp = ((coex_ver >> 16) & 0x7ff) +
|
(coex_ver >> 27) * 10000;
|
RS_INFO("Coexistence: BTCOEX_20%06d-%04x\n", tmp,
|
(coex_ver & 0xffff));
|
|
memcpy(rtl->total_buf, rtl->fw_buf,
|
rtl->fw_len);
|
if (rtl->config_len)
|
memcpy(rtl->total_buf +
|
rtl->fw_len,
|
rtl->config_buf,
|
rtl->config_len);
|
rtl->dl_fw_flag = 1;
|
goto free_buf;
|
}
|
}
|
}
|
|
if (memcmp(rtl->fw_buf, RTK_EPATCH_SIGNATURE, 8)) {
|
RS_DBG("check signature error!");
|
rtl->dl_fw_flag = 0;
|
goto free_buf;
|
}
|
|
if (memcmp
|
(rtl->fw_buf + rtl->fw_len - 4,
|
Extension_Section_SIGNATURE, 4)) {
|
RS_ERR("check extension section signature error");
|
rtl->dl_fw_flag = 0;
|
goto free_buf;
|
}
|
|
proj_id = rtk_get_fw_project_id(rtl->fw_buf + rtl->fw_len - 5);
|
|
#ifdef RTL_8703A_SUPPORT
|
if (rtl->hci_ver == 0x4 && rtl->lmp_subver == ROM_LMP_8723b) {
|
RS_DBG("HCI version = 0x4, IC is 8703A.");
|
} else {
|
RS_ERR("error: lmp_version %x, hci_version %x, project_id %x",
|
rtl->lmp_subver, rtl->hci_ver, project_id[proj_id]);
|
rtk_hw_cfg.dl_fw_flag = 0;
|
goto free_buf;
|
}
|
#else
|
if (rtl->lmp_subver != ROM_LMP_8703b) {
|
if (rtl->lmp_subver != project_id[proj_id]) {
|
RS_ERR("lmp_subver %04x, project id %04x, mismatch\n",
|
rtl->lmp_subver, project_id[proj_id]);
|
rtl->dl_fw_flag = 0;
|
goto free_buf;
|
}
|
} else {
|
/* if (rtk_hw_cfg.patch_ent->proj_id != project_id[proj_id]) {
|
* RS_ERR("proj_id %04x, version %04x from firmware "
|
* "project_id[%u], mismatch\n",
|
* rtk_hw_cfg.patch_ent->proj_id,
|
* project_id[proj_id], proj_id);
|
* rtk_hw_cfg.dl_fw_flag = 0;
|
* goto free_buf;
|
* } */
|
}
|
#endif
|
|
entry = rtk_get_patch_entry();
|
|
if (entry)
|
rtl->total_len =
|
entry->patch_length + rtl->config_len;
|
else {
|
rtl->dl_fw_flag = 0;
|
goto free_buf;
|
}
|
|
if (!(rtl->total_buf = malloc(rtl->total_len))) {
|
RS_ERR("Can't alloc memory for multi fw&config, errno:%d",
|
errno);
|
rtl->dl_fw_flag = 0;
|
rtl->total_len = 0;
|
goto free_buf;
|
} else {
|
memcpy(rtl->total_buf,
|
rtl->fw_buf + entry->start_offset,
|
entry->patch_length);
|
memcpy(rtl->total_buf + entry->patch_length - 4,
|
&patch->fw_version, 4);
|
|
if (rtl->config_len)
|
memcpy(rtl->total_buf + entry->patch_length,
|
rtl->config_buf, rtl->config_len);
|
rtl->dl_fw_flag = 1;
|
}
|
|
RS_DBG("fw:%s exists, config file:%s exists",
|
(rtl->fw_len > 0) ? "" : "not",
|
(rtl->config_len > 0) ? "" : "not");
|
|
free_buf:
|
if (rtl->fw_len > 0) {
|
free(rtl->fw_buf);
|
rtl->fw_len = 0;
|
}
|
|
if (rtl->config_len > 0) {
|
free(rtl->config_buf);
|
rtl->config_len = 0;
|
}
|
|
if (entry)
|
free(entry);
|
}
|
|
/**
|
* Config realtek Bluetooth. The configuration parameter is get from config file and fw.
|
* Config file is rtk8723_bt_config. which is set in rtk_get_bt_config.
|
* fw file is "rlt8723a_chip_b_cut_bt40_fw", which is set in get_firmware_name.
|
*
|
* @warning maybe only one of config file and fw file exists. The bt_addr arg is stored in "/data/btmac.txt"
|
* or "/data/misc/bluetoothd/bt_mac/btmac.txt",
|
*
|
* @param fd uart file descriptor
|
* @param proto realtek Bluetooth protocol, shall be either HCI_UART_H4 or HCI_UART_3WIRE
|
* @param speed init_speed in uart struct
|
* @param ti termios struct
|
* @returns #0 on success
|
*/
|
static int rtk_config(int fd, int proto, int speed, struct termios *ti)
|
{
|
int final_speed = 0;
|
int ret = 0;
|
struct btrtl_info *btrtl = &rtk_hw_cfg;
|
|
btrtl->proto = proto;
|
|
/* Get Local Version Information and RTK ROM version */
|
if (proto == HCI_UART_3WIRE) {
|
RS_INFO("H5 IC");
|
rtk_get_lmp_version(fd);
|
rtk_get_eversion(fd);
|
} else {
|
RS_INFO("H4 IC");
|
rtk_hci_local_ver(fd);
|
rtk_hci_rom_ver(fd);
|
if (rtk_hw_cfg.lmp_subver == ROM_LMP_8761btc) {
|
rtk_hw_cfg.chip_type = CHIP_8761BTC;
|
rtk_hw_cfg.hw_flow_control = 1;
|
/* TODO: Change to different uart baud */
|
uart_speed_to_rtk_speed(1500000, &rtk_hw_cfg.baudrate);
|
goto change_baud;
|
}
|
|
if (rtk_hw_cfg.lmp_subver == ROM_LMP_8761a) {
|
if (rtk_hw_cfg.hci_rev == 0x000b) {
|
rtk_hw_cfg.chip_type = CHIP_8761B;
|
rtk_hw_cfg.hw_flow_control = 1;
|
/* TODO: Change to different uart baud */
|
uart_speed_to_rtk_speed(1500000, &rtk_hw_cfg.baudrate);
|
goto change_baud;
|
} else if (rtk_hw_cfg.hci_rev == 0x000a) {
|
if (rtk_hw_cfg.eversion == 3)
|
rtk_hw_cfg.chip_type = CHIP_8761ATF;
|
else if (rtk_hw_cfg.eversion == 2)
|
rtk_hw_cfg.chip_type = CHIP_8761AT;
|
else
|
rtk_hw_cfg.chip_type = CHIP_UNKNOWN;
|
}
|
}
|
}
|
|
RS_INFO("LMP Subversion 0x%04x", btrtl->lmp_subver);
|
RS_INFO("EVersion %d", btrtl->eversion);
|
|
switch (rtk_hw_cfg.lmp_subver) {
|
case ROM_LMP_8723a:
|
break;
|
case ROM_LMP_8723b:
|
#ifdef RTL_8703A_SUPPORT
|
/* Set chip type for matching fw/config entry */
|
rtl->chip_type = CHIP_8703AS;
|
#endif
|
break;
|
case ROM_LMP_8821a:
|
break;
|
case ROM_LMP_8761a:
|
break;
|
case ROM_LMP_8703b:
|
rtk_get_chip_type(fd);
|
break;
|
}
|
|
btrtl->patch_ent = get_patch_entry(btrtl);
|
if (btrtl->patch_ent) {
|
RS_INFO("IC: %s\n", btrtl->patch_ent->ic_name);
|
RS_INFO("Firmware/config: %s, %s\n",
|
btrtl->patch_ent->patch_file,
|
btrtl->patch_ent->config_file);
|
} else {
|
RS_ERR("Can not find firmware/config entry\n");
|
return -1;
|
}
|
|
rtk_hw_cfg.config_len =
|
rtk_get_bt_config(btrtl, &btrtl->config_buf, &btrtl->baudrate);
|
if (rtk_hw_cfg.config_len < 0) {
|
RS_ERR("Get Config file error, just use efuse settings");
|
rtk_hw_cfg.config_len = 0;
|
}
|
|
rtk_hw_cfg.fw_len = rtk_get_bt_firmware(btrtl, &btrtl->fw_buf);
|
if (rtk_hw_cfg.fw_len < 0) {
|
RS_ERR("Get BT firmware error");
|
rtk_hw_cfg.fw_len = 0;
|
return -1;
|
} else {
|
rtk_get_final_patch(fd, proto);
|
}
|
|
if (rtk_hw_cfg.total_len > RTK_PATCH_LENGTH_MAX) {
|
RS_ERR("total length of fw&config larger than allowed");
|
return -1;
|
}
|
|
RS_INFO("Total len %d for fw/config", rtk_hw_cfg.total_len);
|
|
change_baud:
|
/* change baudrate if needed
|
* rtk_hw_cfg.baudrate is a __u32/__u16 vendor-specific variable
|
* parsed from config file
|
* */
|
if (rtk_hw_cfg.baudrate == 0) {
|
uart_speed_to_rtk_speed(speed, &rtk_hw_cfg.baudrate);
|
RS_DBG("No cfg file, set baudrate, : %u, 0x%08x",
|
(unsigned int)speed, (unsigned int)rtk_hw_cfg.baudrate);
|
goto SET_FLOW_CONTRL;
|
} else
|
rtk_speed_to_uart_speed(rtk_hw_cfg.baudrate,
|
(RT_U32 *) & (rtk_hw_cfg.final_speed));
|
|
if (proto == HCI_UART_3WIRE)
|
rtk_vendor_change_speed_h5(fd, rtk_hw_cfg.baudrate);
|
else
|
rtk_vendor_change_speed_h4(fd, rtk_hw_cfg.baudrate);
|
|
usleep(50000);
|
final_speed = rtk_hw_cfg.final_speed ? rtk_hw_cfg.final_speed : speed;
|
RS_DBG("final_speed %d\n", final_speed);
|
if (set_speed(fd, ti, final_speed) < 0) {
|
RS_ERR("Can't set baud rate:%x, %x, %x", final_speed,
|
rtk_hw_cfg.final_speed, speed);
|
return -1;
|
}
|
|
SET_FLOW_CONTRL:
|
if (rtk_hw_cfg.hw_flow_control) {
|
RS_DBG("hw flow control enable");
|
ti->c_cflag |= CRTSCTS;
|
|
if (tcsetattr(fd, TCSANOW, ti) < 0) {
|
RS_ERR("Can't set port settings");
|
return -1;
|
}
|
} else {
|
RS_DBG("hw flow control disable");
|
ti->c_cflag &= ~CRTSCTS;
|
}
|
|
/* wait for while for controller to setup */
|
usleep(10000);
|
|
/* For 8761B Test chip, no patch to download */
|
if (rtk_hw_cfg.chip_type == CHIP_8761BTC ||
|
rtk_hw_cfg.chip_type == CHIP_8761B)
|
goto done;
|
|
if ((rtk_hw_cfg.total_len > 0) && (rtk_hw_cfg.dl_fw_flag)) {
|
rtk_hw_cfg.link_estab_state = H5_PATCH;
|
rtk_hw_cfg.rx_index = -1;
|
|
ret =
|
rtk_download_fw_config(fd, rtk_hw_cfg.total_buf,
|
rtk_hw_cfg.total_len,
|
rtk_hw_cfg.baudrate, proto, ti);
|
free(rtk_hw_cfg.total_buf);
|
|
if (ret < 0)
|
return ret;
|
}
|
|
done:
|
RS_DBG("Init Process finished");
|
return 0;
|
}
|
|
/**
|
* Init uart by realtek Bluetooth.
|
*
|
* @param fd uart file descriptor
|
* @param proto realtek Bluetooth protocol, shall be either HCI_UART_H4 or HCI_UART_3WIRE
|
* @param speed init_speed in uart struct
|
* @param ti termios struct
|
* @returns #0 on success, depend on rtk_config
|
*/
|
int rtk_init(int fd, int proto, int speed, struct termios *ti)
|
{
|
struct sigaction sa;
|
int retlen;
|
RS_DBG("Realtek hciattach version %s \n", RTK_VERSION);
|
|
memset(&rtk_hw_cfg, 0, sizeof(rtk_hw_cfg));
|
rtk_hw_cfg.serial_fd = fd;
|
rtk_hw_cfg.dl_fw_flag = 1;
|
|
/* h4 will do nothing for init */
|
if (proto == HCI_UART_3WIRE) {
|
if (rtk_init_h5(fd, ti) < 0)
|
return -1;;
|
}
|
|
return rtk_config(fd, proto, speed, ti);
|
}
|
|
/**
|
* Post uart by realtek Bluetooth. If gFinalSpeed is set, set uart speed with it.
|
*
|
* @param fd uart file descriptor
|
* @param proto realtek Bluetooth protocol, shall be either HCI_UART_H4 or HCI_UART_3WIRE
|
* @param ti termios struct
|
* @returns #0 on success.
|
*/
|
int rtk_post(int fd, int proto, struct termios *ti)
|
{
|
if (rtk_hw_cfg.final_speed)
|
return set_speed(fd, ti, rtk_hw_cfg.final_speed);
|
|
return 0;
|
}
|