/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2007-2008 Texas Instruments, Inc.
* Copyright (C) 2005-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#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 <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include "hciattach.h"
#ifdef HCIATTACH_DEBUG
#define DPRINTF(x...) printf(x)
#else
#define DPRINTF(x...)
#endif
#define HCIUARTGETDEVICE _IOR('U', 202, int)
#define MAKEWORD(a, b) ((uint16_t)(((uint8_t)(a)) | ((uint16_t)((uint8_t)(b))) << 8))
#define TI_MANUFACTURER_ID 13
#define FIRMWARE_DIRECTORY "/lib/firmware/"
#define ACTION_SEND_COMMAND 1
#define ACTION_WAIT_EVENT 2
#define ACTION_SERIAL 3
#define ACTION_DELAY 4
#define ACTION_RUN_SCRIPT 5
#define ACTION_REMARKS 6
#define BRF_DEEP_SLEEP_OPCODE_BYTE_1 0x0c
#define BRF_DEEP_SLEEP_OPCODE_BYTE_2 0xfd
#define BRF_DEEP_SLEEP_OPCODE \
(BRF_DEEP_SLEEP_OPCODE_BYTE_1 | (BRF_DEEP_SLEEP_OPCODE_BYTE_2 << 8))
#define FILE_HEADER_MAGIC 0x42535442
/*
* BRF Firmware header
*/
struct bts_header {
uint32_t magic;
uint32_t version;
uint8_t future[24];
uint8_t actions[0];
}__attribute__ ((packed));
/*
* BRF Actions structure
*/
struct bts_action {
uint16_t type;
uint16_t size;
uint8_t data[0];
} __attribute__ ((packed));
struct bts_action_send {
uint8_t data[0];
} __attribute__ ((packed));
struct bts_action_wait {
uint32_t msec;
uint32_t size;
uint8_t data[0];
}__attribute__ ((packed));
struct bts_action_delay {
uint32_t msec;
}__attribute__ ((packed));
struct bts_action_serial {
uint32_t baud;
uint32_t flow_control;
}__attribute__ ((packed));
static FILE *bts_load_script(const char* file_name, uint32_t* version)
{
struct bts_header header;
FILE* fp;
fp = fopen(file_name, "rb");
if (!fp) {
perror("can't open firmware file");
goto out;
}
if (1 != fread(&header, sizeof(struct bts_header), 1, fp)) {
perror("can't read firmware file");
goto errclose;
}
if (header.magic != FILE_HEADER_MAGIC) {
fprintf(stderr, "%s not a legal TI firmware file\n", file_name);
goto errclose;
}
if (NULL != version)
*version = header.version;
goto out;
errclose:
fclose(fp);
fp = NULL;
out:
return fp;
}
static unsigned long bts_fetch_action(FILE* fp, unsigned char* action_buf,
unsigned long buf_size, uint16_t* action_type)
{
struct bts_action action_hdr;
unsigned long nread;
if (!fp)
return 0;
if (1 != fread(&action_hdr, sizeof(struct bts_action), 1, fp))
return 0;
if (action_hdr.size > buf_size) {
fprintf(stderr, "bts_next_action: not enough space to read next action\n");
return 0;
}
nread = fread(action_buf, sizeof(uint8_t), action_hdr.size, fp);
if (nread != (action_hdr.size)) {
fprintf(stderr, "bts_next_action: fread failed to read next action\n");
return 0;
}
*action_type = action_hdr.type;
return nread * sizeof(uint8_t);
}
static void bts_unload_script(FILE* fp)
{
if (fp)
fclose(fp);
}
static int is_it_texas(const uint8_t *respond)
{
uint16_t manufacturer_id;
manufacturer_id = MAKEWORD(respond[11], respond[12]);
return TI_MANUFACTURER_ID == manufacturer_id ? 1 : 0;
}
static const char *get_firmware_name(const uint8_t *respond)
{
static char firmware_file_name[PATH_MAX] = {0};
uint16_t version = 0, chip = 0, min_ver = 0, maj_ver = 0;
version = MAKEWORD(respond[13], respond[14]);
chip = (version & 0x7C00) >> 10;
min_ver = (version & 0x007F);
maj_ver = (version & 0x0380) >> 7;
if (version & 0x8000)
maj_ver |= 0x0008;
sprintf(firmware_file_name, FIRMWARE_DIRECTORY "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver);
return firmware_file_name;
}
static void brf_delay(struct bts_action_delay *delay)
{
usleep(1000 * delay->msec);
}
static int brf_set_serial_params(struct bts_action_serial *serial_action,
int fd, struct termios *ti)
{
fprintf(stderr, "texas: changing baud rate to %u, flow control to %u\n",
serial_action->baud, serial_action->flow_control );
tcflush(fd, TCIOFLUSH);
if (serial_action->flow_control)
ti->c_cflag |= CRTSCTS;
else
ti->c_cflag &= ~CRTSCTS;
if (tcsetattr(fd, TCSANOW, ti) < 0) {
perror("Can't set port settings");
return -1;
}
tcflush(fd, TCIOFLUSH);
if (set_speed(fd, ti, serial_action->baud) < 0) {
perror("Can't set baud rate");
return -1;
}
return 0;
}
static int brf_send_command_socket(int fd, struct bts_action_send* send_action)
{
char response[1024] = {0};
hci_command_hdr *cmd = (hci_command_hdr *) send_action->data;
uint16_t opcode = cmd->opcode;
struct hci_request rq;
memset(&rq, 0, sizeof(rq));
rq.ogf = cmd_opcode_ogf(opcode);
rq.ocf = cmd_opcode_ocf(opcode);
rq.event = EVT_CMD_COMPLETE;
rq.cparam = &send_action->data[3];
rq.clen = send_action->data[2];
rq.rparam = response;
rq.rlen = sizeof(response);
if (hci_send_req(fd, &rq, 15) < 0) {
perror("Cannot send hci command to socket");
return -1;
}
/* verify success */
if (response[0]) {
errno = EIO;
return -1;
}
return 0;
}
static int brf_send_command_file(int fd, struct bts_action_send* send_action, long size)
{
unsigned char response[1024] = {0};
long ret = 0;
/* send command */
if (size != write(fd, send_action, size)) {
perror("Texas: Failed to write action command");
return -1;
}
/* read response */
ret = read_hci_event(fd, response, sizeof(response));
if (ret < 0) {
perror("texas: failed to read command response");
return -1;
}
/* verify success */
if (ret < 7 || 0 != response[6]) {
fprintf( stderr, "TI init command failed.\n" );
errno = EIO;
return -1;
}
return 0;
}
static int brf_send_command(int fd, struct bts_action_send* send_action, long size, int hcill_installed)
{
int ret = 0;
char *fixed_action;
/* remove packet type when giving to socket API */
if (hcill_installed) {
fixed_action = ((char *) send_action) + 1;
ret = brf_send_command_socket(fd, (struct bts_action_send *) fixed_action);
} else {
ret = brf_send_command_file(fd, send_action, size);
}
return ret;
}
static int brf_do_action(uint16_t brf_type, uint8_t *brf_action, long brf_size,
int fd, struct termios *ti, int hcill_installed)
{
int ret = 0;
switch (brf_type) {
case ACTION_SEND_COMMAND:
DPRINTF("W");
ret = brf_send_command(fd, (struct bts_action_send*) brf_action, brf_size, hcill_installed);
break;
case ACTION_WAIT_EVENT:
DPRINTF("R");
break;
case ACTION_SERIAL:
DPRINTF("S");
ret = brf_set_serial_params((struct bts_action_serial *) brf_action, fd, ti);
break;
case ACTION_DELAY:
DPRINTF("D");
brf_delay((struct bts_action_delay *) brf_action);
break;
case ACTION_REMARKS:
DPRINTF("C");
break;
default:
fprintf(stderr, "brf_init: unknown firmware action type (%d)\n", brf_type);
break;
}
return ret;
}
/*
* tests whether a given brf action is a HCI_VS_Sleep_Mode_Configurations cmd
*/
static int brf_action_is_deep_sleep(uint8_t *brf_action, long brf_size,
uint16_t brf_type)
{
uint16_t opcode;
if (brf_type != ACTION_SEND_COMMAND)
return 0;
if (brf_size < 3)
return 0;
if (brf_action[0] != HCI_COMMAND_PKT)
return 0;
/* HCI data is little endian */
opcode = brf_action[1] | (brf_action[2] << 8);
if (opcode != BRF_DEEP_SLEEP_OPCODE)
return 0;
/* action is deep sleep configuration command ! */
return 1;
}
/*
* This function is called twice.
* The first time it is called, it loads the brf script, and executes its
* commands until it reaches a deep sleep command (or its end).
* The second time it is called, it assumes HCILL protocol is set up,
* and sends rest of brf script via the supplied socket.
*/
static int brf_do_script(int fd, struct termios *ti, const char *bts_file)
{
int ret = 0, hcill_installed = bts_file ? 0 : 1;
uint32_t vers;
static FILE *brf_script_file = NULL;
static uint8_t brf_action[512];
static long brf_size;
static uint16_t brf_type;
/* is it the first time we are called ? */
if (0 == hcill_installed) {
DPRINTF("Sending script to serial device\n");
brf_script_file = bts_load_script(bts_file, &vers );
if (!brf_script_file) {
fprintf(stderr, "Warning: cannot find BTS file: %s\n",
bts_file);
return 0;
}
fprintf( stderr, "Loaded BTS script version %u\n", vers );
brf_size = bts_fetch_action(brf_script_file, brf_action,
sizeof(brf_action), &brf_type);
if (brf_size == 0) {
fprintf(stderr, "Warning: BTS file is empty !");
return 0;
}
}
else {
DPRINTF("Sending script to bluetooth socket\n");
}
/* execute current action and continue to parse brf script file */
while (brf_size != 0) {
ret = brf_do_action(brf_type, brf_action, brf_size,
fd, ti, hcill_installed);
if (ret == -1)
break;
brf_size = bts_fetch_action(brf_script_file, brf_action,
sizeof(brf_action), &brf_type);
/* if this is the first time we run (no HCILL yet) */
/* and a deep sleep command is encountered */
/* we exit */
if (!hcill_installed &&
brf_action_is_deep_sleep(brf_action,
brf_size, brf_type))
return 0;
}
bts_unload_script(brf_script_file);
brf_script_file = NULL;
DPRINTF("\n");
return ret;
}
int texas_init(int fd, struct termios *ti)
{
struct timespec tm = {0, 50000};
char cmd[4];
unsigned char resp[100]; /* Response */
const char *bts_file;
int n;
memset(resp,'\0', 100);
/* It is possible to get software version with manufacturer specific
HCI command HCI_VS_TI_Version_Number. But the only thing you get more
is if this is point-to-point or point-to-multipoint module */
/* Get Manufacturer and LMP version */
cmd[0] = HCI_COMMAND_PKT;
cmd[1] = 0x01;
cmd[2] = 0x10;
cmd[3] = 0x00;
do {
n = write(fd, cmd, 4);
if (n < 0) {
perror("Failed to write init command (READ_LOCAL_VERSION_INFORMATION)");
return -1;
}
if (n < 4) {
fprintf(stderr, "Wanted to write 4 bytes, could only write %d. Stop\n", n);
return -1;
}
/* Read reply. */
if (read_hci_event(fd, resp, 100) < 0) {
perror("Failed to read init response (READ_LOCAL_VERSION_INFORMATION)");
return -1;
}
/* Wait for command complete event for our Opcode */
} while (resp[4] != cmd[1] && resp[5] != cmd[2]);
/* Verify manufacturer */
if (! is_it_texas(resp)) {
fprintf(stderr,"ERROR: module's manufacturer is not Texas Instruments\n");
return -1;
}
fprintf(stderr, "Found a Texas Instruments' chip!\n");
bts_file = get_firmware_name(resp);
fprintf(stderr, "Firmware file : %s\n", bts_file);
n = brf_do_script(fd, ti, bts_file);
nanosleep(&tm, NULL);
return n;
}
int texas_post(int fd, struct termios *ti)
{
int dev_id, dd, ret = 0;
sleep(1);
dev_id = ioctl(fd, HCIUARTGETDEVICE, 0);
if (dev_id < 0) {
perror("cannot get device id");
return -1;
}
DPRINTF("\nAdded device hci%d\n", dev_id);
dd = hci_open_dev(dev_id);
if (dd < 0) {
perror("HCI device open failed");
return -1;
}
if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) {
fprintf(stderr, "Can't init device hci%d: %s (%d)", dev_id,
strerror(errno), errno);
hci_close_dev(dd);
return -1;
}
ret = brf_do_script(dd, ti, NULL);
hci_close_dev(dd);
return ret;
}