blob: 749098ef92e036f07def3e3cd2262df1e7bdb998 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2012 Intel Corporation. All rights reserved.
*
*
* 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 <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <time.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include "hciattach.h"
#ifdef INTEL_DEBUG
#define DBGPRINT(fmt, args...) printf("DBG: " fmt "\n", ## args)
#define PRINT_PACKET(buf, len, msg) { \
int i; \
printf("%s\n", msg); \
for (i = 0; i < len; i++) \
printf("%02X ", buf[i]); \
printf("\n"); \
}
#else
#define DBGPRINT(fmt, args...)
#define PRINT_PACKET(buf, len, msg)
#endif
#define PATCH_SEQ_EXT ".bseq"
#define PATCH_FILE_PATH "/lib/firmware/intel/"
#define PATCH_MAX_LEN 260
#define PATCH_TYPE_CMD 1
#define PATCH_TYPE_EVT 2
#define INTEL_VER_PARAM_LEN 9
#define INTEL_MFG_PARAM_LEN 2
/**
* A data structure for a patch entry.
*/
struct patch_entry {
int type;
int len;
unsigned char data[PATCH_MAX_LEN];
};
/**
* A structure for patch context
*/
struct patch_ctx {
int dev;
int fd;
int patch_error;
int reset_enable_patch;
};
/**
* Send HCI command to the controller
*/
static int intel_write_cmd(int dev, unsigned char *buf, int len)
{
int ret;
PRINT_PACKET(buf, len, "<----- SEND CMD: ");
ret = write(dev, buf, len);
if (ret < 0)
return -errno;
if (ret != len)
return -1;
return ret;
}
/**
* Read the event from the controller
*/
static int intel_read_evt(int dev, unsigned char *buf, int len)
{
int ret;
ret = read_hci_event(dev, buf, len);
if (ret < 0)
return -1;
PRINT_PACKET(buf, ret, "-----> READ EVT: ");
return ret;
}
/**
* Validate HCI events
*/
static int validate_events(struct patch_entry *event,
struct patch_entry *entry)
{
if (event == NULL || entry == NULL) {
DBGPRINT("invalid patch entry parameters");
return -1;
}
if (event->len != entry->len) {
DBGPRINT("lengths are mismatched:[%d|%d]",
event->len, entry->len);
return -1;
}
if (memcmp(event->data, entry->data, event->len)) {
DBGPRINT("data is mismatched");
return -1;
}
return 0;
}
/**
* Read the next patch entry one line at a time
*/
static int get_next_patch_entry(int fd, struct patch_entry *entry)
{
int size;
char rb;
if (read(fd, &rb, 1) <= 0)
return 0;
entry->type = rb;
switch (entry->type) {
case PATCH_TYPE_CMD:
entry->data[0] = HCI_COMMAND_PKT;
if (read(fd, &entry->data[1], 3) < 0)
return -1;
size = (int)entry->data[3];
if (read(fd, &entry->data[4], size) < 0)
return -1;
entry->len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE + size;
break;
case PATCH_TYPE_EVT:
entry->data[0] = HCI_EVENT_PKT;
if (read(fd, &entry->data[1], 2) < 0)
return -1;
size = (int)entry->data[2];
if (read(fd, &entry->data[3], size) < 0)
return -1;
entry->len = HCI_TYPE_LEN + HCI_EVENT_HDR_SIZE + size;
break;
default:
fprintf(stderr, "invalid patch entry(%d)\n", entry->type);
return -1;
}
return entry->len;
}
/**
* Download the patch set to the controller and verify the event
*/
static int intel_download_patch(struct patch_ctx *ctx)
{
int ret;
struct patch_entry entry;
struct patch_entry event;
DBGPRINT("start patch downloading");
do {
ret = get_next_patch_entry(ctx->fd, &entry);
if (ret <= 0) {
ctx->patch_error = 1;
break;
}
switch (entry.type) {
case PATCH_TYPE_CMD:
ret = intel_write_cmd(ctx->dev,
entry.data,
entry.len);
if (ret <= 0) {
fprintf(stderr, "failed to send cmd(%d)\n",
ret);
return ret;
}
break;
case PATCH_TYPE_EVT:
ret = intel_read_evt(ctx->dev, event.data,
sizeof(event.data));
if (ret <= 0) {
fprintf(stderr, "failed to read evt(%d)\n",
ret);
return ret;
}
event.len = ret;
if (validate_events(&event, &entry) < 0) {
DBGPRINT("events are mismatched");
ctx->patch_error = 1;
return -1;
}
break;
default:
fprintf(stderr, "unknown patch type(%d)\n",
entry.type);
return -1;
}
} while (1);
return ret;
}
static int open_patch_file(struct patch_ctx *ctx, char *fw_ver)
{
char patch_file[PATH_MAX];
snprintf(patch_file, PATH_MAX, "%s%s%s", PATCH_FILE_PATH,
fw_ver, PATCH_SEQ_EXT);
DBGPRINT("PATCH_FILE: %s", patch_file);
ctx->fd = open(patch_file, O_RDONLY);
if (ctx->fd < 0) {
DBGPRINT("cannot open patch file. go to post patch");
return -1;
}
return 0;
}
/**
* Prepare the controller for patching.
*/
static int pre_patch(struct patch_ctx *ctx)
{
int ret, i;
struct patch_entry entry;
char fw_ver[INTEL_VER_PARAM_LEN * 2];
DBGPRINT("start pre_patch");
entry.data[0] = HCI_COMMAND_PKT;
entry.data[1] = 0x11;
entry.data[2] = 0xFC;
entry.data[3] = 0x02;
entry.data[4] = 0x01;
entry.data[5] = 0x00;
entry.len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE + INTEL_MFG_PARAM_LEN;
ret = intel_write_cmd(ctx->dev, entry.data, entry.len);
if (ret < 0) {
fprintf(stderr, "failed to send cmd(%d)\n", ret);
return ret;
}
ret = intel_read_evt(ctx->dev, entry.data, sizeof(entry.data));
if (ret < 0) {
fprintf(stderr, "failed to read evt(%d)\n", ret);
return ret;
}
entry.len = ret;
if (entry.data[6] != 0x00) {
DBGPRINT("command failed. status=%02x", entry.data[6]);
ctx->patch_error = 1;
return -1;
}
entry.data[0] = HCI_COMMAND_PKT;
entry.data[1] = 0x05;
entry.data[2] = 0xFC;
entry.data[3] = 0x00;
entry.len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE;
ret = intel_write_cmd(ctx->dev, entry.data, entry.len);
if (ret < 0) {
fprintf(stderr, "failed to send cmd(%d)\n", ret);
return ret;
}
ret = intel_read_evt(ctx->dev, entry.data, sizeof(entry.data));
if (ret < 0) {
fprintf(stderr, "failed to read evt(%d)\n", ret);
return ret;
}
entry.len = ret;
if (entry.data[6] != 0x00) {
DBGPRINT("command failed. status=%02x", entry.data[6]);
ctx->patch_error = 1;
return -1;
}
for (i = 0; i < INTEL_VER_PARAM_LEN; i++)
sprintf(&fw_ver[i*2], "%02x", entry.data[7+i]);
if (open_patch_file(ctx, fw_ver) < 0) {
ctx->patch_error = 1;
return -1;
}
return ret;
}
/*
* check the event is startup event
*/
static int is_startup_evt(unsigned char *buf)
{
if (buf[1] == 0xFF && buf[2] == 0x01 && buf[3] == 0x00)
return 1;
return 0;
}
/**
* Finalize the patch process and reset the controller
*/
static int post_patch(struct patch_ctx *ctx)
{
int ret;
struct patch_entry entry;
DBGPRINT("start post_patch");
entry.data[0] = HCI_COMMAND_PKT;
entry.data[1] = 0x11;
entry.data[2] = 0xFC;
entry.data[3] = 0x02;
entry.data[4] = 0x00;
if (ctx->reset_enable_patch)
entry.data[5] = 0x02;
else
entry.data[5] = 0x01;
entry.len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE + INTEL_MFG_PARAM_LEN;
ret = intel_write_cmd(ctx->dev, entry.data, entry.len);
if (ret < 0) {
fprintf(stderr, "failed to send cmd(%d)\n", ret);
return ret;
}
ret = intel_read_evt(ctx->dev, entry.data, sizeof(entry.data));
if (ret < 0) {
fprintf(stderr, "failed to read evt(%d)\n", ret);
return ret;
}
entry.len = ret;
if (entry.data[6] != 0x00) {
fprintf(stderr, "cmd failed. st=%02x\n", entry.data[6]);
return -1;
}
do {
ret = intel_read_evt(ctx->dev, entry.data,
sizeof(entry.data));
if (ret < 0) {
fprintf(stderr, "failed to read cmd(%d)\n", ret);
return ret;
}
entry.len = ret;
} while (!is_startup_evt(entry.data));
return ret;
}
/**
* Main routine that handles the device patching process.
*/
static int intel_patch_device(struct patch_ctx *ctx)
{
int ret;
ret = pre_patch(ctx);
if (ret < 0) {
if (!ctx->patch_error) {
fprintf(stderr, "I/O error: pre_patch failed\n");
return ret;
}
DBGPRINT("patch failed. proceed to post patch");
goto post_patch;
}
ret = intel_download_patch(ctx);
if (ret < 0) {
if (!ctx->patch_error) {
fprintf(stderr, "I/O error: download_patch failed\n");
close(ctx->fd);
return ret;
}
} else {
DBGPRINT("patch done");
ctx->reset_enable_patch = 1;
}
close(ctx->fd);
post_patch:
ret = post_patch(ctx);
if (ret < 0) {
fprintf(stderr, "post_patch failed(%d)\n", ret);
return ret;
}
return 0;
}
static int set_rts(int dev, int rtsval)
{
int arg;
if (ioctl(dev, TIOCMGET, &arg) < 0) {
perror("cannot get TIOCMGET");
return -errno;
}
if (rtsval)
arg |= TIOCM_RTS;
else
arg &= ~TIOCM_RTS;
if (ioctl(dev, TIOCMSET, &arg) == -1) {
perror("cannot set TIOCMGET");
return -errno;
}
return 0;
}
static unsigned char get_intel_speed(int speed)
{
switch (speed) {
case 9600:
return 0x00;
case 19200:
return 0x01;
case 38400:
return 0x02;
case 57600:
return 0x03;
case 115200:
return 0x04;
case 230400:
return 0x05;
case 460800:
return 0x06;
case 921600:
return 0x07;
case 1843200:
return 0x08;
case 3250000:
return 0x09;
case 2000000:
return 0x0A;
case 3000000:
return 0x0B;
default:
return 0xFF;
}
}
/**
* if it failed to change to new baudrate, it will rollback
* to initial baudrate
*/
static int change_baudrate(int dev, int init_speed, int *speed,
struct termios *ti)
{
int ret;
unsigned char br;
unsigned char cmd[5];
unsigned char evt[7];
DBGPRINT("start baudrate change");
ret = set_rts(dev, 0);
if (ret < 0) {
fprintf(stderr, "failed to clear RTS\n");
return ret;
}
cmd[0] = HCI_COMMAND_PKT;
cmd[1] = 0x06;
cmd[2] = 0xFC;
cmd[3] = 0x01;
br = get_intel_speed(*speed);
if (br == 0xFF) {
fprintf(stderr, "speed %d is not supported\n", *speed);
return -1;
}
cmd[4] = br;
ret = intel_write_cmd(dev, cmd, sizeof(cmd));
if (ret < 0) {
fprintf(stderr, "failed to send cmd(%d)\n", ret);
return ret;
}
/*
* wait for buffer to be consumed by the controller
*/
usleep(300000);
if (set_speed(dev, ti, *speed) < 0) {
fprintf(stderr, "can't set to new baud rate\n");
return -1;
}
ret = set_rts(dev, 1);
if (ret < 0) {
fprintf(stderr, "failed to set RTS\n");
return ret;
}
ret = intel_read_evt(dev, evt, sizeof(evt));
if (ret < 0) {
fprintf(stderr, "failed to read evt(%d)\n", ret);
return ret;
}
if (evt[4] != 0x00) {
fprintf(stderr,
"failed to change speed. use default speed %d\n",
init_speed);
*speed = init_speed;
}
return 0;
}
/**
* An entry point for Intel specific initialization
*/
int intel_init(int dev, int init_speed, int *speed, struct termios *ti)
{
int ret = 0;
struct patch_ctx ctx;
if (change_baudrate(dev, init_speed, speed, ti) < 0)
return -1;
ctx.dev = dev;
ctx.patch_error = 0;
ctx.reset_enable_patch = 0;
ret = intel_patch_device(&ctx);
if (ret < 0)
fprintf(stderr, "failed to initialize the device");
return ret;
}