--- /dev/null
+/*
+ *
+ * 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;
+}