bluez/tools/hciattach_intel.c
Tedd Ho-Jeong An 8d25f9528b hciattach: Add support for Intel Bluetooth device
This patch enables the Intel Bluetooth device (UART sku) over the H4
protocol.  It is responsible for bring up the device into known state by
configuring the baudrate and applying the patches, if required.
2012-05-16 10:36:20 +03:00

597 lines
11 KiB
C

/*
*
* 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 len, size;
char rb;
if (read(fd, &rb, 1) <= 0)
return 0;
entry->type = rb;
len = 0;
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[len], 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 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;
}